Compare commits
147 Commits
36b1df7ef8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c7af1901aa | |||
| 2473bfa702 | |||
| 4dd842b3b8 | |||
| 89ef04cc6f | |||
| 3cec883356 | |||
| 0ecbe29ec1 | |||
| 188331c1ad | |||
| 486e4fb6b8 | |||
| c775bb3354 | |||
| b6968b7b67 | |||
| a0aa75c5a0 | |||
| 78be07c8bb | |||
| 0575a34422 | |||
| 3bc3801ffb | |||
| 4368111311 | |||
| daf9e8a966 | |||
| 8a08d3eac6 | |||
| a761a3634b | |||
| a1a30cffd1 | |||
| 6a631be909 | |||
| 75c17d2065 | |||
| 63c053b38c | |||
| 5bcbdaf3d0 | |||
| 074032f20d | |||
| 13e282e815 | |||
| 6c8ac33be7 | |||
| 92ce51eb7c | |||
| 52ef39fd5c | |||
| 623e19f028 | |||
| 14dd87e335 | |||
| 52956ecaa4 | |||
| cc51807819 | |||
| 8e039037a9 | |||
| ed40f4c77e | |||
| ccddef8ba8 | |||
| 05e31f4b9e | |||
| d2578b8850 | |||
| 392a9ef407 | |||
| 2d2337257f | |||
| 5a95bf3ef0 | |||
| 7e9401d8bb | |||
| 0c4465c91a | |||
| 5f9d49561a | |||
| 253d998b68 | |||
| 9acfd1ccd0 | |||
| 39d23f4a8a | |||
| f41a1b3363 | |||
| 1e649d1f23 | |||
| 5cc1fbe919 | |||
| 528d7af031 | |||
| f0bcea0405 | |||
| 67b5976176 | |||
| 4ef2d90aa8 | |||
| 7539b1653d | |||
| 6072afc8c0 | |||
| 0fb2ec5273 | |||
| fdf14b06c8 | |||
| a23b6a6e9e | |||
| 5a9636cdc1 | |||
| 3fdcc110e3 | |||
| 452bdbedb4 | |||
| 9c1599f8c3 | |||
| 5cb71e5547 | |||
| c0d67c2a16 | |||
| 26b018ee3e | |||
| 44fdf49555 | |||
| afe795edd5 | |||
| a0856d171a | |||
| 5ed4a6c081 | |||
| 7a4840ef95 | |||
| c3473ca60e | |||
| 646735565f | |||
| a354004201 | |||
| 942be59715 | |||
| 70e376c939 | |||
| 62a1ad83ab | |||
| 47c5a47ffa | |||
| 9bd6e1fc04 | |||
| 73600055fc | |||
| 80907a89a9 | |||
| 2472374157 | |||
| b72a10db94 | |||
| d097988dfd | |||
| cb9e8e1107 | |||
| 7bb7df788e | |||
| f26cf6404e | |||
| 7f867b02d6 | |||
| 8c04e7ace5 | |||
| 74a05a39e1 | |||
| f11e402d16 | |||
| 19130e361d | |||
| af0670b9a0 | |||
| a236d96971 | |||
| 2fa142708e | |||
| 71b5017c82 | |||
| ac9ce206dd | |||
| 2b9a859e7e | |||
| 1e3b755b88 | |||
| 46579ac6d9 | |||
| cded5982d4 | |||
| e6041b03ee | |||
| 7cbc8c8b84 | |||
| 7b1a0b054b | |||
| d6dce0908e | |||
| 3661cf5c25 | |||
| b25debabb2 | |||
| 2646ecbdd2 | |||
| d7bfeabed1 | |||
| 83305031fd | |||
| 12c338581a | |||
| 8044547488 | |||
| 2397fc2004 | |||
| 120ee75bf7 | |||
| 8b814222c8 | |||
| c81ae4ee1c | |||
| 9e6d82b44d | |||
| 10fcdbe89f | |||
| 5342e60811 | |||
| ab1d8fa556 | |||
| 84091ecdba | |||
| 1ed753506d | |||
| 3d5a8ee464 | |||
| 1980a184e4 | |||
| 3d803dffdb | |||
| e3cba33ea1 | |||
| b07c33e5a3 | |||
| 8ba663c038 | |||
| 6a7fcadd2f | |||
| 710bdd878a | |||
| aeb154ad83 | |||
| d4575b15a4 | |||
| 1172fe8a7c | |||
| b7809c94d9 | |||
| b82438f2f5 | |||
| 78527307b2 | |||
| 211bad836e | |||
| 96292589b3 | |||
| 799be62f3c | |||
| 81bcc648e3 | |||
| 2386a14d7a | |||
| 5df8c092be | |||
| f06b621b8d | |||
| cd064539eb | |||
| cf2484705a | |||
| afab215a9e | |||
| 6c7f17893b | |||
| a282940d9b |
35
.env-example
Normal file
35
.env-example
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# uncomment this out to run in productions
|
||||||
|
# APP_ENV=production
|
||||||
|
|
||||||
|
# Server port that will allow vite to talk to the backend.
|
||||||
|
VITE_SERVER_PORT=4000
|
||||||
|
|
||||||
|
# lstv2 loc
|
||||||
|
LSTV2="C\drive\loc"
|
||||||
|
|
||||||
|
# discord - this us used to monitor the logs and make sure we never have a critial shut down.
|
||||||
|
# this will be for other critical stuff like nice label and some other events to make sure we are still in a good spot and dont need to jump in
|
||||||
|
WEBHOOK=
|
||||||
|
|
||||||
|
# dev stuff below
|
||||||
|
|
||||||
|
# Gitea Info
|
||||||
|
GITEA_URL=git repo
|
||||||
|
GITEA_USERNAME=username
|
||||||
|
GITEA_REPO=logistics_support_tool
|
||||||
|
GITEA_TOKEN=ad8eac91a01e3a1885a1dc10
|
||||||
|
|
||||||
|
# postgres db
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5433
|
||||||
|
DB_USER=username
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_NAME=lst # db must be created before you start the app
|
||||||
|
|
||||||
|
# dev locs
|
||||||
|
DEV_FOLDER=C\drive\loc
|
||||||
|
ADMUSER=username
|
||||||
|
ADMPASSWORD=password
|
||||||
|
|
||||||
|
# Build number info
|
||||||
|
BUILD_NAME=leBlfRaj
|
||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -4,6 +4,13 @@ frontend/.tanstack/
|
|||||||
frontend/.output/
|
frontend/.output/
|
||||||
frontend/.nitro/
|
frontend/.nitro/
|
||||||
releases/
|
releases/
|
||||||
|
LstWrapper/bin
|
||||||
|
LstWrapper/publish
|
||||||
|
LstWrapper/obj
|
||||||
|
scripts/tmp
|
||||||
|
backend/docs
|
||||||
|
backend/frontend
|
||||||
|
testFolder
|
||||||
|
|
||||||
# ---> Go
|
# ---> Go
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
@@ -183,3 +190,8 @@ test-results/
|
|||||||
|
|
||||||
backend/go.sum
|
backend/go.sum
|
||||||
BUILD_NUMBER
|
BUILD_NUMBER
|
||||||
|
scripts/resetDanger.js
|
||||||
|
LstWrapper/Program_vite_as_Static.txt
|
||||||
|
LstWrapper/Program_proxy_backend.txt
|
||||||
|
scripts/stopPool.go
|
||||||
|
backend_bad_practice
|
||||||
@@ -1,31 +1,62 @@
|
|||||||
{
|
{
|
||||||
"git": {
|
"git": {
|
||||||
"commitMessage": "chore(release): v${version}",
|
"commitMessage": "chore(release): v${version}",
|
||||||
"tagName": "v${version}",
|
"tagName": "v${version}",
|
||||||
"requireCleanWorkingDir": true
|
"requireCleanWorkingDir": true,
|
||||||
},
|
"addUntrackedFiles": true
|
||||||
"npm": {
|
},
|
||||||
"publish": false
|
"npm": {
|
||||||
},
|
"publish": false
|
||||||
"hooks": {
|
},
|
||||||
"after:bump": "echo Version bumped to ${version}",
|
"hooks": {
|
||||||
"after:release": "node ./scripts/create-gitea-release.js ${version}"
|
"before:init": "node ./scripts/read-build-number.js",
|
||||||
|
"after:release": "node ./scripts/create-gitea-release.js ${version}"
|
||||||
|
},
|
||||||
|
"github": false,
|
||||||
|
"plugins": {
|
||||||
|
"@release-it/conventional-changelog": {
|
||||||
|
"preset": {
|
||||||
|
"name": "conventionalcommits",
|
||||||
|
"types": [
|
||||||
|
{ "type": "feat", "section": "🌟 Enhancements" },
|
||||||
|
{ "type": "fix", "section": "🐛 Bug fixes" },
|
||||||
|
{ "type": "chore", "section": "📝 Chore" },
|
||||||
|
{ "type": "docs", "section": "📚 Documentation" },
|
||||||
|
{ "type": "style", "section": "📚 Style" },
|
||||||
|
{ "type": "refactor", "section": "🛠️ Code Refactor" },
|
||||||
|
{
|
||||||
|
"type": "perf",
|
||||||
|
|
||||||
},
|
"section": "🚀 Performance"
|
||||||
"github": false,
|
},
|
||||||
"plugins": {
|
{ "type": "test", "section": "📝 Testing Code" },
|
||||||
"@release-it/conventional-changelog": {
|
{
|
||||||
"preset": "conventionalcommits",
|
"type": "ci",
|
||||||
"infile": "CHANGELOG.md",
|
|
||||||
"config": ".versionrc.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitea": {
|
|
||||||
"host": "https://${GITEA_URL}",
|
|
||||||
"tokenRef": "GITEA_TOKEN"
|
|
||||||
},
|
|
||||||
|
|
||||||
"files": [
|
"section": "📈 Project changes"
|
||||||
"package.json"
|
},
|
||||||
]
|
{
|
||||||
|
"type": "build",
|
||||||
|
"hidden": false,
|
||||||
|
"section": "📈 Project Builds"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commitUrlFormat": "{{host}}/{{owner}}/{{repository}}/commit/{{hash}}",
|
||||||
|
"compareUrlFormat": "{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}",
|
||||||
|
"issueUrlFormat": "{{host}}/{{owner}}/{{repository}}/issues/{{id}}",
|
||||||
|
"userUrlFormat": "{{host}}/{{user}}"
|
||||||
|
},
|
||||||
|
"infile": "CHANGELOG.md",
|
||||||
|
"header": "# Changelog\n\nAll notable changes to LST will be documented in this file.\n",
|
||||||
|
"releaseCommitMessageFormat": "chore(release): {{currentTag}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@release-it/gitea": {
|
||||||
|
"host": "https://${GITEA_URL}",
|
||||||
|
"tokenRef": "GITEA_TOKEN",
|
||||||
|
"releaseName": "v${version} (build ${BUILD_NUMBER})",
|
||||||
|
"releaseNotes": "node ./scripts/get-changelog-entry.js ${version}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"files": ["package.json", "CHANGELOG.md"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"types": [
|
|
||||||
{ "type": "feat", "section": "🌟 Enhancements" },
|
|
||||||
{ "type": "fix", "section": "🐛 Bug fixes" },
|
|
||||||
{ "type": "chore", "hidden": false, "section": "📝 Chore" },
|
|
||||||
{ "type": "docs", "section": "📚 Documentation" },
|
|
||||||
{ "type": "style", "hidden": true },
|
|
||||||
{ "type": "refactor", "section": "🛠️ Code Refactor" },
|
|
||||||
{ "type": "perf", "hidden": false, "section": "🚀 Performance" },
|
|
||||||
{ "type": "test", "section": "📝 Testing Code" },
|
|
||||||
{ "type": "ci", "hidden": false, "section": "📈 Project changes" },
|
|
||||||
{ "type": "build", "hidden": true, "section": "📈 Project Builds" }
|
|
||||||
],
|
|
||||||
"commitUrlFormat": "https://git.tuffraid.net/cowch/logistics_support_tool/commits/{{hash}}",
|
|
||||||
"compareUrlFormat": "https://git.tuffraid.net/cowch/logistics_support_tool/compare/{{previousTag}}...{{currentTag}}",
|
|
||||||
"header": "# All changes to lst are shown below.\nReleases are combined zip of backend and frontend."
|
|
||||||
}
|
|
||||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -24,5 +24,12 @@
|
|||||||
},
|
},
|
||||||
"[handlebars]": {
|
"[handlebars]": {
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
}
|
},
|
||||||
|
"[go]": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "golang.go"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Optional: Configure goimports instead of gofmt
|
||||||
|
"go.formatTool": "goimports"
|
||||||
}
|
}
|
||||||
388
CHANGELOG.md
388
CHANGELOG.md
@@ -1,112 +1,342 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to LST will be documented in this file.
|
||||||
|
|
||||||
|
|
||||||
|
## [0.0.1-alpha.6](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.5...v0.0.1-alpha.6) (2025-07-31)
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **logging:** added in db and logging with websocket ([52ef39f](https://git.tuffraid.net/cowch/logistics_support_tool/commit/52ef39fd5c129ed02ed9f38dbf7e49ae06807ad6))
|
||||||
|
* **settings:** migrated all settings endpoints confirmed as well for updates ([0575a34](https://git.tuffraid.net/cowch/logistics_support_tool/commit/0575a344229ba0ff5c0f47781c6d596e5c08e5eb))
|
||||||
|
* **ws server:** added in a websocket on port system to help with better logging ([5bcbdaf](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5bcbdaf3d0e889729d4dce3df51f4330d7793868))
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **update server:** fixed to make sure everything is stopped before doing the remaining update ([13e282e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/13e282e815c1c95a0a5298ede2f6497cdf036440))
|
||||||
|
* **websocket:** errors in saving client info during ping ping ([4368111](https://git.tuffraid.net/cowch/logistics_support_tool/commit/4368111311c48e73a11a6b24febdcc3be31a2a59))
|
||||||
|
* **wrapper:** corrections to properly handle websockets :D ([a761a36](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a761a3634b6cb0aeeb571dd634bd158cee530779))
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
* **.env example:** added postrgres example ([14dd87e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/14dd87e335a63d76d64c07a15cf593cb286a9833))
|
||||||
|
* **dockerbuild:** comments as a reminder for my seld ([52956ec](https://git.tuffraid.net/cowch/logistics_support_tool/commit/52956ecaa45cd556ba7832d6cb9ec2cf883d983a))
|
||||||
|
* **docker:** docs about the custom network for the db is seperated ([6a631be](https://git.tuffraid.net/cowch/logistics_support_tool/commit/6a631be909b56a899af393510edffd70d7901a7a))
|
||||||
|
* **wss:** more ws stuff ([63c053b](https://git.tuffraid.net/cowch/logistics_support_tool/commit/63c053b38ce3ab3c3a94cda620da930f4e8615bd))
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **app port:** changed to have the port be dyncamic on the iis side ([074032f](https://git.tuffraid.net/cowch/logistics_support_tool/commit/074032f20dc90810416c5899e44fefe86b52f98a))
|
||||||
|
* **build:** added back in the build name stuff ([92ce51e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/92ce51eb7cf14ebb599c29fea4721e21badafbf6))
|
||||||
|
* **config:** changed to settings to match the other lst in node. makes it more easy to manage ([3bc3801](https://git.tuffraid.net/cowch/logistics_support_tool/commit/3bc3801ffbb544a814d52c72e566e8d4866a7f38))
|
||||||
|
* **createzip:** added in env-example to the zip file ([6c8ac33](https://git.tuffraid.net/cowch/logistics_support_tool/commit/6c8ac33be73f203137b883e33feb625ccc0945e9))
|
||||||
|
* **docker compose example:** added in postgress stuff plus network ([623e19f](https://git.tuffraid.net/cowch/logistics_support_tool/commit/623e19f028d27fbfc46bee567ce78169cddba8fb))
|
||||||
|
* **settings:** changed config to settings and added in the update method for this as well ([a0aa75c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a0aa75c5a0b4a6e3a10b88bbcccf43d096e532b4))
|
||||||
|
* **wrapper:** removed the logger stuff so we dont fill up space ([8a08d3e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/8a08d3eac6540b00ff23115936d56b4f22f16d53))
|
||||||
|
* **ws:** ws logging and channel manager added no auth currently ([a1a30cf](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a1a30cffd18e02e1061959fa3164f8237522880c))
|
||||||
|
|
||||||
|
### 🚀 Performance
|
||||||
|
|
||||||
|
* **websocket:** added in base url to help with ssl stuff and iis ([daf9e8a](https://git.tuffraid.net/cowch/logistics_support_tool/commit/daf9e8a966fd440723b1aec932a02873a5e27eb7))
|
||||||
|
|
||||||
|
### 📝 Testing Code
|
||||||
|
|
||||||
|
* **iis:** wrapper test for ws ([75c17d2](https://git.tuffraid.net/cowch/logistics_support_tool/commit/75c17d20659dcc5a762e00928709c4d3dd277284))
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **hotreload:** added in air for hot reloading ([78be07c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/78be07c8bbf5acbcdac65351f693941f47be4cb5))
|
||||||
|
|
||||||
|
## [0.0.1-alpha.5](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.4...v0.0.1-alpha.5) (2025-07-21)
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **backend:** env added ([67b5976](https://git.tuffraid.net/cowch/logistics_support_tool/commit/67b59761769350951bc6b52ef715b592b5d4a862))
|
||||||
|
* **backend:** set the static path to the docs ([5a9636c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5a9636cdc15f164ed3547f544e34858683b38241))
|
||||||
|
* **docs:** added in the new docs to build alongside the backend ([3fdcc11](https://git.tuffraid.net/cowch/logistics_support_tool/commit/3fdcc110e3b4d7356af1fb025070bdf7413a8e88))
|
||||||
|
* **env-example:** added in an example env ([a23b6a6](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a23b6a6e9eef4dbeb2f84c325ca8dca178ab3ff3))
|
||||||
|
* **env:** new env introduced to handle dev and prodution ([f41a1b3](https://git.tuffraid.net/cowch/logistics_support_tool/commit/f41a1b336389d6100e42681f53d9f618c8726f25))
|
||||||
|
* **services:** just a 1 to 1 from lstv2 ([8e03903](https://git.tuffraid.net/cowch/logistics_support_tool/commit/8e039037a9b40994b9e77f59680a3ce1b6ebc3a0))
|
||||||
|
* **update server:** new update server added with iis stop and old version included ([05e31f4](https://git.tuffraid.net/cowch/logistics_support_tool/commit/05e31f4b9e20799257da244b237420fa2b6435f8))
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **backend:** ignored docs this should be built before running by the user ([fdf14b0](https://git.tuffraid.net/cowch/logistics_support_tool/commit/fdf14b06c88d3057f31184e03fb592bd9a959847))
|
||||||
|
* **builds:** added in the gets/installs for our go project and node portons ([528d7af](https://git.tuffraid.net/cowch/logistics_support_tool/commit/528d7af0312cb3de43ddc93d8af22bde6aadea52))
|
||||||
|
* **docs:** added a copy script to cp the build to the backend with ps1 ([0fb2ec5](https://git.tuffraid.net/cowch/logistics_support_tool/commit/0fb2ec52739def8294a57bbc6c497ba6531568a6))
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
* **env:** changes to have more clear info on the example doc ([5f9d495](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5f9d49561a7c22a4d2cb85bd06bbbdb9fa952224))
|
||||||
|
* **iiscontrol:** added in an example how to run i t ([ccddef8](https://git.tuffraid.net/cowch/logistics_support_tool/commit/ccddef8ba8f64774db6d99fba3ea7c7c54bea1a5))
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **backend:** changes to convert the backend to strictly the app ([1e649d1](https://git.tuffraid.net/cowch/logistics_support_tool/commit/1e649d1f23c404252754d746254810386bb0f233))
|
||||||
|
* **build:** changes to remove the build name as it was not really realvent ([392a9ef](https://git.tuffraid.net/cowch/logistics_support_tool/commit/392a9ef407d4e64f573cbfc9109c8a81f55c14d5))
|
||||||
|
* **config:** changes to autoformat go files ([4ef2d90](https://git.tuffraid.net/cowch/logistics_support_tool/commit/4ef2d90aa8595d5e3d18a289c012320bdf0dcc4a))
|
||||||
|
* **createzip:** added in verbage to be clear what was done at the end ([452bdbe](https://git.tuffraid.net/cowch/logistics_support_tool/commit/452bdbedb48cde7fa0ef246fb61e304127c49e58))
|
||||||
|
* **createzip:** changes to the way the app looks for better understanding ([d2578b8](https://git.tuffraid.net/cowch/logistics_support_tool/commit/d2578b885029ca98b750f4c6996e567053b2e517))
|
||||||
|
* **createzip:** renamed the backend to app now that everything is in one ([0c4465c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/0c4465c91a40cbea73048617952df26b476d01f4))
|
||||||
|
* **docker:** removed frontend from being built ([9acfd1c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/9acfd1ccd0b9019d5acc01153ddad15b82b6c74e))
|
||||||
|
* **env-example:** changes to the env example to include the dev stuff ([2d23372](https://git.tuffraid.net/cowch/logistics_support_tool/commit/2d2337257f2b31c740f9eb7064be010528e14c7c))
|
||||||
|
* **frontend:** changes to no longer use server side and only static files ([39d23f4](https://git.tuffraid.net/cowch/logistics_support_tool/commit/39d23f4a8a8af6b1acb87b913e5cd1929fe144e4))
|
||||||
|
* **lstv2build:** changes to not always build the old app if we dont need too ([ed40f4c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/ed40f4c77e9d81e36292f38c342e9c2b062f84b9))
|
||||||
|
* **lstv2:** moved the loc to .env file ([7539b16](https://git.tuffraid.net/cowch/logistics_support_tool/commit/7539b1653d7a48dbe248847ed321ab065e58efa0))
|
||||||
|
* **wrapper:** changes to handle docs and frontned now ([253d998](https://git.tuffraid.net/cowch/logistics_support_tool/commit/253d998b68b5808d6fd2d9731255616238fcdb71))
|
||||||
|
|
||||||
|
### 🚀 Performance
|
||||||
|
|
||||||
|
* **docs:** changes to stop the server from opening a browser when it started up ([f0bcea0](https://git.tuffraid.net/cowch/logistics_support_tool/commit/f0bcea0405db364e1e15471b5db74fc9d8f93788))
|
||||||
|
|
||||||
|
### 📝 Testing Code
|
||||||
|
|
||||||
|
* **app:** added production into the build so we dont fill logs up ([7e9401d](https://git.tuffraid.net/cowch/logistics_support_tool/commit/7e9401d8bb12589de6ce1517e4502d784788ab0f))
|
||||||
|
* **docker:** changes to make all latest now instead of 2 apps ([5a95bf3](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5a95bf3ef0f4d4b125f8e777d57cc96b3d3a894d))
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **wrapper:** changes to clean the publish folder ([5cc1fbe](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5cc1fbe919a8c3fa46617f7b9ed2830559b8978f))
|
||||||
|
|
||||||
|
### 📈 Project Builds
|
||||||
|
|
||||||
|
* **docs:** added building the docs into the build script ([6072afc](https://git.tuffraid.net/cowch/logistics_support_tool/commit/6072afc8c01a4fe4980e0e17cfe41d5a9cd524ef))
|
||||||
|
|
||||||
|
## [0.0.1-alpha.4](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.3...v0.0.1-alpha.4) (2025-07-16)
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **build:** changes to ignore the tmp folder in the scripts ([26b018e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/26b018ee3eb408ab3c5ddd9e886987deb3334720))
|
||||||
|
* **createzip:** changes to include the script folder ([5cb71e5](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5cb71e55476f91a48d095f80be2cea3b6793f8e6))
|
||||||
|
|
||||||
|
### 📈 Project Builds
|
||||||
|
|
||||||
|
* **build:** script created to build lstv2 and copy over before zipping up also cleanup ([c0d67c2](https://git.tuffraid.net/cowch/logistics_support_tool/commit/c0d67c2a16695e40af7ec52f86fc54344f3b889c))
|
||||||
|
|
||||||
|
## [0.0.1-alpha.3](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.2...v0.0.1-alpha.3) (2025-07-16)
|
||||||
|
|
||||||
|
### 🌟 Enhancements
|
||||||
|
|
||||||
|
* **scripts:** 2 new scripts for the build process ([afe795e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/afe795edd588b823e5459a6762f787152a49a8cb))
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **build:** changes to show more details when we run the build stuff ([5ed4a6c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5ed4a6c081e29da6600949ed2b811551e6a947f7))
|
||||||
|
* **createzip:** change the name from release to createZip ([6467355](https://git.tuffraid.net/cowch/logistics_support_tool/commit/646735565f28153c0750e7788b349b384bd5c5de))
|
||||||
|
* **package.json:** changes to the scripts ([7a4840e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/7a4840ef95c86a96e3b8a2e8af124a55af2b01b4))
|
||||||
|
* **release-it:** changes to the build process and changelog ([c3473ca](https://git.tuffraid.net/cowch/logistics_support_tool/commit/c3473ca60e7e92f68cdff4e4a35bfcff2e7ae37b))
|
||||||
|
* **releases:** simplified the functions ([a0856d1](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a0856d171a404c3adf52591b3c9da4fae53322d3))
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [0.0.1-alpha.2](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.1...v0.0.1-alpha.2) (2025-07-16)
|
||||||
|
|
||||||
|
## [0.0.3-alpha.22](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.21...v0.0.3-alpha.22) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **changelog:** moved packages ([f11e402](https://git.tuffraid.net/cowch/logistics_support_tool/commit/f11e402d16c750c3001ceebbdfd2f073de1e2703))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.21](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.19...v0.0.3-alpha.21) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **package.json:** corrections to the commit changelog stuff ([a236d96](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a236d9697198e5474a8c311984c22ae6f0d1901e))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.20](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.19...v0.0.3-alpha.20) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **package.json:** corrections to the commit changelog stuff ([a236d96](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a236d9697198e5474a8c311984c22ae6f0d1901e))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.19](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.18...v0.0.3-alpha.19) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **changelog:** readd the header back in ([71b5017](https://git.tuffraid.net/cowch/logistics_support_tool/commit/71b5017c82e718f1c63f3862c0953d1554e7cf5b))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.18](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.17...v0.0.3-alpha.18) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **build:** minor change to the build system to trigger new tags ([2b9a859](https://git.tuffraid.net/cowch/logistics_support_tool/commit/2b9a859e7e944eee590d0d51b95fa13405d21790))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.17](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.16...v0.0.3-alpha.17) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **changelog:** changes here ([46579ac](https://git.tuffraid.net/cowch/logistics_support_tool/commit/46579ac6d9819085d183eba1dafb6b8873f2dd0e))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.16](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.15...v0.0.3-alpha.16) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **h:** sssdasjhksdf ([e6041b0](https://git.tuffraid.net/cowch/logistics_support_tool/commit/e6041b03ee38888fa4e53a498b0414941ff701de))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.15](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.14...v0.0.3-alpha.15) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **d:** d ([7b1a0b0](https://git.tuffraid.net/cowch/logistics_support_tool/commit/7b1a0b054b05e33ecd754b91a080fc83ff214630))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.14](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.13...v0.0.3-alpha.14) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **changelog:** more change log stuff ([3661cf5](https://git.tuffraid.net/cowch/logistics_support_tool/commit/3661cf5c25b919035853a135bc89d1fc3d5df428))
|
||||||
|
|
||||||
|
## [0.0.3-alpha.12](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.11...v0.0.3-alpha.12) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** re ([8330503](https://git.tuffraid.net/cowch/logistics_support_tool/commit/83305031fd8bbf7465269bd517a318d12f1e8414))
|
||||||
|
## [0.0.3-alpha.11](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.10...v0.0.3-alpha.11) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bogus:** this is crap ([8044547](https://git.tuffraid.net/cowch/logistics_support_tool/commit/80445474881da87ee431d0c3212dcd458a3ec69d))
|
||||||
|
## [0.0.3-alpha.10](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.9...v0.0.3-alpha.10) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **jfhsldkfhja:** ghjskdhkflasfdfg ([120ee75](https://git.tuffraid.net/cowch/logistics_support_tool/commit/120ee75bf763a8a98aae1fd8f00925eee203af2a))
|
||||||
|
## [0.0.3-alpha.9](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.8...v0.0.3-alpha.9) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **fucl:** fuck ([c81ae4e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/c81ae4ee1c28854313705786d1d03d9886abaca0))
|
||||||
|
## [0.0.3-alpha.8](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.7...v0.0.3-alpha.8) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **change:** fuck ([10fcdbe](https://git.tuffraid.net/cowch/logistics_support_tool/commit/10fcdbe89f54548800fbbca86a011ecb8a9c9234))
|
||||||
|
## [0.0.3-alpha.7](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.6...v0.0.3-alpha.7) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** changes ([ab1d8fa](https://git.tuffraid.net/cowch/logistics_support_tool/commit/ab1d8fa5562f82f566d9777e61634e90bb6eb1e3))
|
||||||
|
## [0.0.3-alpha.6](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.5...v0.0.3-alpha.6) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** more changes ([1ed7535](https://git.tuffraid.net/cowch/logistics_support_tool/commit/1ed753506d040e94fd8eb9c2978d96e8f38f1fc5))
|
||||||
|
## [0.0.3-alpha.5](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.4...v0.0.3-alpha.5) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** changes to the changelog portion so its no longer a link ([1980a18](https://git.tuffraid.net/cowch/logistics_support_tool/commit/1980a184e4a6c4ddbd43f24771453e88d0e03198))
|
||||||
|
## [0.0.3-alpha.4](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.3...v0.0.3-alpha.4) (2025-07-12)
|
||||||
|
## [0.0.3-alpha.3](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.2...v0.0.3-alpha.3) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** more fixes ([6a7fcad](https://git.tuffraid.net/cowch/logistics_support_tool/commit/6a7fcadd2f8c52faec33bab7b2e73b0ef9943895))
|
||||||
|
## [0.0.3-alpha.2](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.3-alpha.1...v0.0.3-alpha.2) (2025-07-12)
|
||||||
|
## [0.0.3-alpha.1](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2...v0.0.3-alpha.1) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** something ([b7809c9](https://git.tuffraid.net/cowch/logistics_support_tool/commit/b7809c94d96a2168804e0b82413537af1c064b31))
|
||||||
|
## [0.0.2](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.15...v0.0.2) (2025-07-12)
|
||||||
|
## [0.0.2-alpha.15](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.14...v0.0.2-alpha.15) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** cahgnes ([799be62](https://git.tuffraid.net/cowch/logistics_support_tool/commit/799be62f3c77631a4108ee9e92f19157ab597dff))
|
||||||
|
## [0.0.2-alpha.14](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.13...v0.0.2-alpha.14) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** corrections ([2386a14](https://git.tuffraid.net/cowch/logistics_support_tool/commit/2386a14d7a76f89653a13de3a0d86e1bf55934d5))
|
||||||
|
## [0.0.2-alpha.13](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.12...v0.0.2-alpha.13) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** conflicting tags ([f06b621](https://git.tuffraid.net/cowch/logistics_support_tool/commit/f06b621b8dc0cc8fc7084fa259408f0faafb6b74))
|
||||||
|
## [0.0.2-alpha.12](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.11...v0.0.2-alpha.12) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** change to get the release to build then release stuff ([afab215](https://git.tuffraid.net/cowch/logistics_support_tool/commit/afab215a9e29732d8236c45c4a8417c9b1e02b1e))
|
||||||
|
* **release:** removed the intial before bump ([cf24847](https://git.tuffraid.net/cowch/logistics_support_tool/commit/cf2484705a65d065ec75690e21bf6e8f2e32a0f5))
|
||||||
|
## [0.0.2-alpha.11](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.10...v0.0.2-alpha.11) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** var not defined ([a282940](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a282940d9b1334743af31b96fbb3fd8228ee290a))
|
||||||
## [0.0.2-alpha.10](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.9...v0.0.2-alpha.10) (2025-07-12)
|
## [0.0.2-alpha.10](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.9...v0.0.2-alpha.10) (2025-07-12)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* **release:** error in function ([dc4beb2](https://git.tuffraid.net/cowch/logistics_support_tool/commit/dc4beb2d2768c8e5f5f3f629b13bb38373c2a0a2))
|
* **release:** error in function ([dc4beb2](https://git.tuffraid.net/cowch/logistics_support_tool/commit/dc4beb2d2768c8e5f5f3f629b13bb38373c2a0a2))
|
||||||
|
## [0.0.2-alpha.9](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.8...v0.0.2-alpha.9) (2025-07-12)
|
||||||
|
|
||||||
## <small>0.0.2-alpha.8 (2025-07-12)</small>
|
### Bug Fixes
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.8 ([f7da816](https://git.tuffraid.net/cowch/logistics_support_tool/commits/f7da816))
|
* **release:** checking the build file failes but not sure why ([f429bd4](https://git.tuffraid.net/cowch/logistics_support_tool/commit/f429bd43ab85df0419c1f06fb551d9af5fd418ae))
|
||||||
* build(build): linked everything together ([fdf5e25](https://git.tuffraid.net/cowch/logistics_support_tool/commits/fdf5e25))
|
## [0.0.2-alpha.8](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.7...v0.0.2-alpha.8) (2025-07-12)
|
||||||
* ci(default): added in default configs so they work across everything ([8cb416e](https://git.tuffraid.net/cowch/logistics_support_tool/commits/8cb416e))
|
## [0.0.2-alpha.7](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.6...v0.0.2-alpha.7) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **relrease:** maybe finally got it ([c0aa64d](https://git.tuffraid.net/cowch/logistics_support_tool/commit/c0aa64db634cff5a6597215035929f16b0edfca0))
|
||||||
|
## [0.0.2-alpha.6](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.5...v0.0.2-alpha.6) (2025-07-12)
|
||||||
|
|
||||||
## <small>0.0.2-alpha.7 (2025-07-12)</small>
|
### Bug Fixes
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.7 ([2c265d7](https://git.tuffraid.net/cowch/logistics_support_tool/commits/2c265d7))
|
* **release fixes:** more fixes ([6fc69fc](https://git.tuffraid.net/cowch/logistics_support_tool/commit/6fc69fc6bcbda7f978dbd332ef23ef0501653c50))
|
||||||
* fix(relrease): maybe finally got it ([c0aa64d](https://git.tuffraid.net/cowch/logistics_support_tool/commits/c0aa64d))
|
## [0.0.2-alpha.5](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.4...v0.0.2-alpha.5) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** more fixes ([65873bd](https://git.tuffraid.net/cowch/logistics_support_tool/commit/65873bd31f4c4e5cc0a77aa171822cd842353e45))
|
||||||
|
## [0.0.2-alpha.4](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.3...v0.0.2-alpha.4) (2025-07-12)
|
||||||
|
|
||||||
## <small>0.0.2-alpha.6 (2025-07-12)</small>
|
### Bug Fixes
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.6 ([8622408](https://git.tuffraid.net/cowch/logistics_support_tool/commits/8622408))
|
* **package:** added changes to the package.json so we see the release stuff ([494ee3f](https://git.tuffraid.net/cowch/logistics_support_tool/commit/494ee3f58ea5af3d18a640ffff99918f460fb4aa))
|
||||||
* fix(release fixes): more fixes ([6fc69fc](https://git.tuffraid.net/cowch/logistics_support_tool/commits/6fc69fc))
|
## [0.0.2-alpha.3](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.2...v0.0.2-alpha.3) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **release:** more bug fixes ([5b7f9f2](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5b7f9f2c482531ea52100bff191b943e218e72df))
|
||||||
|
* **release:** more work on the release stuff ([233298d](https://git.tuffraid.net/cowch/logistics_support_tool/commit/233298daa5850178974be675bb8566fa8c0a8b0a))
|
||||||
|
## [0.0.2-alpha.2](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.1...v0.0.2-alpha.2) (2025-07-12)
|
||||||
|
|
||||||
## <small>0.0.2-alpha.5 (2025-07-12)</small>
|
### Bug Fixes
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.5 ([6779d1c](https://git.tuffraid.net/cowch/logistics_support_tool/commits/6779d1c))
|
* **releases:** more fun release stuff ([f33554e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/f33554ed973e65e2488512bfd8359577967698b7))
|
||||||
* fix(release): more fixes ([65873bd](https://git.tuffraid.net/cowch/logistics_support_tool/commits/65873bd))
|
## [0.0.2-alpha.1](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.2-alpha.0...v0.0.2-alpha.1) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **package:** made changes to get the correct alpha passed over in the prerelease stuff ([09e5fdf](https://git.tuffraid.net/cowch/logistics_support_tool/commit/09e5fdf209f1a11f975d77e445f777623a5d3509))
|
||||||
|
* **release it:** type in how i wrote the release-it file ([8664703](https://git.tuffraid.net/cowch/logistics_support_tool/commit/86647034c8f5c9b04c6f51fb95b929de4f65fc63))
|
||||||
|
* **release:** removed version from being passed over ([30b39b8](https://git.tuffraid.net/cowch/logistics_support_tool/commit/30b39b836a8ac01aea3f7a4ddf3b6ec25d6a36cd))
|
||||||
|
## [0.0.1-alpha.4.14-cowch](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.3.14-cowch...v0.0.1-alpha.4.14-cowch) (2025-07-12)
|
||||||
|
|
||||||
## <small>0.0.2-alpha.4 (2025-07-12)</small>
|
### Bug Fixes
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.4 ([5948c5c](https://git.tuffraid.net/cowch/logistics_support_tool/commits/5948c5c))
|
* **releaseit:** changes to get the version to update ([e084663](https://git.tuffraid.net/cowch/logistics_support_tool/commit/e084663f19ada722007bd00c73a4ca7015a4f71e))
|
||||||
* fix(package): added changes to the package.json so we see the release stuff ([494ee3f](https://git.tuffraid.net/cowch/logistics_support_tool/commits/494ee3f))
|
## [0.0.1-alpha.3.14-cowch](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.2.14-cowch...v0.0.1-alpha.3.14-cowch) (2025-07-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **releaseit:** changes to get it to actaully run ([c680152](https://git.tuffraid.net/cowch/logistics_support_tool/commit/c680152004b85442cad5bf6eeac407de67fead32))
|
||||||
|
## [0.0.1-alpha.2.14-cowch](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.1.14-cowch...v0.0.1-alpha.2.14-cowch) (2025-07-12)
|
||||||
|
|
||||||
## <small>0.0.2-alpha.3 (2025-07-12)</small>
|
### Bug Fixes
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.0 ([f83de9b](https://git.tuffraid.net/cowch/logistics_support_tool/commits/f83de9b))
|
* **package json:** correction to the version ([8141c6a](https://git.tuffraid.net/cowch/logistics_support_tool/commit/8141c6ab698ce246f999ed304d306f72c50a2708))
|
||||||
* chore(release): v0.0.2-alpha.1 ([a087929](https://git.tuffraid.net/cowch/logistics_support_tool/commits/a087929))
|
* **releases:** a few more tweaks to the releasing ([587f1eb](https://git.tuffraid.net/cowch/logistics_support_tool/commit/587f1eb3a2841780903db6c9b6f4fa760baa29e9))
|
||||||
* chore(release): v0.0.2-alpha.2 ([0a9ae10](https://git.tuffraid.net/cowch/logistics_support_tool/commits/0a9ae10))
|
* **releases:** corrections to reduce the build number to be true ([18aa9e7](https://git.tuffraid.net/cowch/logistics_support_tool/commit/18aa9e7670ffd531c0c03b946dad599600bd5c5b))
|
||||||
* chore(release): v0.0.2-alpha.3 ([d266dbd](https://git.tuffraid.net/cowch/logistics_support_tool/commits/d266dbd))
|
## [0.0.1-alpha.1.14-cowch](https://git.tuffraid.net/cowch/logistics_support_tool/compare/0.0.2...v0.0.1-alpha.1.14-cowch) (2025-07-12)
|
||||||
* fix(release): more bug fixes ([5b7f9f2](https://git.tuffraid.net/cowch/logistics_support_tool/commits/5b7f9f2))
|
## v0.0.1-alpha-1 (2025-07-12)
|
||||||
* fix(release): more work on the release stuff ([233298d](https://git.tuffraid.net/cowch/logistics_support_tool/commits/233298d))
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **starter:** intial starter release ([b370cb1](https://git.tuffraid.net/cowch/logistics_support_tool/commit/b370cb17c86bb3f8727cebfa607bb6423fc89644))
|
||||||
|
|
||||||
## <small>0.0.2-alpha.2 (2025-07-12)</small>
|
### Bug Fixes
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.2 ([4b0028f](https://git.tuffraid.net/cowch/logistics_support_tool/commits/4b0028f))
|
* **releases:** spawn fixes to overcome npx and powershell ([eed0119](https://git.tuffraid.net/cowch/logistics_support_tool/commit/eed01197c0825e9039c3a2f0d8845c3b21380faf))
|
||||||
* fix(releases): more fun release stuff ([f33554e](https://git.tuffraid.net/cowch/logistics_support_tool/commits/f33554e))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>0.0.2-alpha.1 (2025-07-12)</small>
|
|
||||||
|
|
||||||
* chore(release): v0.0.2-alpha.0 ([8b40d3f](https://git.tuffraid.net/cowch/logistics_support_tool/commits/8b40d3f))
|
|
||||||
* chore(release): v0.0.2-alpha.1 ([4b9a225](https://git.tuffraid.net/cowch/logistics_support_tool/commits/4b9a225))
|
|
||||||
* fix(package): made changes to get the correct alpha passed over in the prerelease stuff ([09e5fdf](https://git.tuffraid.net/cowch/logistics_support_tool/commits/09e5fdf))
|
|
||||||
* fix(release it): type in how i wrote the release-it file ([8664703](https://git.tuffraid.net/cowch/logistics_support_tool/commits/8664703))
|
|
||||||
* fix(release): removed version from being passed over ([30b39b8](https://git.tuffraid.net/cowch/logistics_support_tool/commits/30b39b8))
|
|
||||||
* ci(pakcage.json): missing plugin ([772463e](https://git.tuffraid.net/cowch/logistics_support_tool/commits/772463e))
|
|
||||||
* refactor(backend): change to just get a version bump ([01e2d40](https://git.tuffraid.net/cowch/logistics_support_tool/commits/01e2d40))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>0.0.1-alpha.4.14-cowch (2025-07-12)</small>
|
|
||||||
|
|
||||||
* fix(releaseit): changes to get the version to update ([e084663](https://git.tuffraid.net/cowch/logistics_support_tool/commits/e084663))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>0.0.1-alpha.3.14-cowch (2025-07-12)</small>
|
|
||||||
|
|
||||||
* fix(releaseit): changes to get it to actaully run ([c680152](https://git.tuffraid.net/cowch/logistics_support_tool/commits/c680152))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>0.0.1-alpha.2.14-cowch (2025-07-12)</small>
|
|
||||||
|
|
||||||
* refactor(package.json): added in prerelease so we can keep it as alpha for now ([e081951](https://git.tuffraid.net/cowch/logistics_support_tool/commits/e081951))
|
|
||||||
* fix(package json): correction to the version ([8141c6a](https://git.tuffraid.net/cowch/logistics_support_tool/commits/8141c6a))
|
|
||||||
* fix(releases): a few more tweaks to the releasing ([587f1eb](https://git.tuffraid.net/cowch/logistics_support_tool/commits/587f1eb))
|
|
||||||
* fix(releases): corrections to reduce the build number to be true ([18aa9e7](https://git.tuffraid.net/cowch/logistics_support_tool/commits/18aa9e7))
|
|
||||||
* build(changelog): commited change log ([508aab3](https://git.tuffraid.net/cowch/logistics_support_tool/commits/508aab3))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>0.0.1-alpha.1.14-cowch (2025-07-12)</small>
|
|
||||||
|
|
||||||
* refactor(versionrc): alloed project changes to show in the changelog ([2a9b571](https://git.tuffraid.net/cowch/logistics_support_tool/commits/2a9b571))
|
|
||||||
* ci(builds): added better logic to building ([320968f](https://git.tuffraid.net/cowch/logistics_support_tool/commits/320968f))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>v0.0.1-alpha-1 (2025-07-12)</small>
|
|
||||||
|
|
||||||
* fix(releases): spawn fixes to overcome npx and powershell ([eed0119](https://git.tuffraid.net/cowch/logistics_support_tool/commits/eed0119))
|
|
||||||
* refactor(releases): refactoring to get the actual release and changelog to show ([a20d377](https://git.tuffraid.net/cowch/logistics_support_tool/commits/a20d377))
|
|
||||||
* ci(package.json): missing scripts for committing ([0b17faf](https://git.tuffraid.net/cowch/logistics_support_tool/commits/0b17faf))
|
|
||||||
* ci(releases): added in release it to add to the build process ([1b07f56](https://git.tuffraid.net/cowch/logistics_support_tool/commits/1b07f56))
|
|
||||||
* feat(starter): intial starter release ([b370cb1](https://git.tuffraid.net/cowch/logistics_support_tool/commits/b370cb1))
|
|
||||||
* Initial commit ([e4d3fc9](https://git.tuffraid.net/cowch/logistics_support_tool/commits/e4d3fc9))
|
|
||||||
* Update README.md ([e081c8f](https://git.tuffraid.net/cowch/logistics_support_tool/commits/e081c8f))
|
|
||||||
|
|||||||
9
LstWrapper/LstWrapper.csproj
Normal file
9
LstWrapper/LstWrapper.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
158
LstWrapper/Program.cs
Normal file
158
LstWrapper/Program.cs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Services.AddHttpClient("GoBackend", client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri("http://localhost:8080");
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Enable WebSocket support
|
||||||
|
app.UseWebSockets();
|
||||||
|
|
||||||
|
// Logging method
|
||||||
|
void LogToFile(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string logDir = Path.Combine(AppContext.BaseDirectory, "logs");
|
||||||
|
Directory.CreateDirectory(logDir);
|
||||||
|
string logFilePath = Path.Combine(logDir, "proxy_log.txt");
|
||||||
|
File.AppendAllText(logFilePath, $"{DateTime.UtcNow}: {message}{Environment.NewLine}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Handle potential errors writing to log file
|
||||||
|
Console.WriteLine($"Logging error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware to handle WebSocket requests
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
if (context.WebSockets.IsWebSocketRequest && context.Request.Path.StartsWithSegments("/ws"))
|
||||||
|
{
|
||||||
|
// LogToFile($"WebSocket request received for path: {context.Request.Path}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var backendUri = new UriBuilder("ws", "localhost", 8080)
|
||||||
|
{
|
||||||
|
Path = context.Request.Path,
|
||||||
|
Query = context.Request.QueryString.ToString()
|
||||||
|
}.Uri;
|
||||||
|
|
||||||
|
using var backendSocket = new ClientWebSocket();
|
||||||
|
await backendSocket.ConnectAsync(backendUri, context.RequestAborted);
|
||||||
|
|
||||||
|
using var frontendSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
// WebSocket forwarding tasks
|
||||||
|
var forwardToBackend = ForwardWebSocketAsync(frontendSocket, backendSocket, cts.Token);
|
||||||
|
var forwardToFrontend = ForwardWebSocketAsync(backendSocket, frontendSocket, cts.Token);
|
||||||
|
|
||||||
|
await Task.WhenAny(forwardToBackend, forwardToFrontend);
|
||||||
|
cts.Cancel();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
//LogToFile($"WebSocket proxy error: {ex.Message}");
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.BadGateway;
|
||||||
|
await context.Response.WriteAsync($"WebSocket proxy error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Middleware to handle HTTP requests
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
if (context.WebSockets.IsWebSocketRequest)
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = context.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient("GoBackend");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(new HttpMethod(context.Request.Method),
|
||||||
|
context.Request.Path + context.Request.QueryString);
|
||||||
|
|
||||||
|
foreach (var header in context.Request.Headers)
|
||||||
|
{
|
||||||
|
if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()))
|
||||||
|
{
|
||||||
|
request.Content ??= new StreamContent(context.Request.Body);
|
||||||
|
request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.ContentLength > 0 && request.Content == null)
|
||||||
|
{
|
||||||
|
request.Content = new StreamContent(context.Request.Body);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
|
||||||
|
context.Response.StatusCode = (int)response.StatusCode;
|
||||||
|
|
||||||
|
foreach (var header in response.Headers)
|
||||||
|
{
|
||||||
|
context.Response.Headers[header.Key] = header.Value.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var header in response.Content.Headers)
|
||||||
|
{
|
||||||
|
context.Response.Headers[header.Key] = header.Value.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.Headers.Remove("transfer-encoding");
|
||||||
|
await response.Content.CopyToAsync(context.Response.Body);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
LogToFile($"HTTP proxy error: {ex.Message}");
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.BadGateway;
|
||||||
|
await context.Response.WriteAsync($"Backend request failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async Task ForwardWebSocketAsync(WebSocket source, WebSocket destination, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var buffer = new byte[4 * 1024];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (source.State == WebSocketState.Open &&
|
||||||
|
destination.State == WebSocketState.Open &&
|
||||||
|
!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var result = await source.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||||
|
if (result.MessageType == WebSocketMessageType.Close)
|
||||||
|
{
|
||||||
|
await destination.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await destination.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (WebSocketException ex)
|
||||||
|
{
|
||||||
|
LogToFile($"WebSocket forwarding error: {ex.Message}");
|
||||||
|
await destination.CloseOutputAsync(WebSocketCloseStatus.InternalServerError, "Error", cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run();
|
||||||
23
LstWrapper/Properties/launchSettings.json
Normal file
23
LstWrapper/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "http://localhost:5015",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:7208;http://localhost:5015",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
LstWrapper/appsettings.Development.json
Normal file
8
LstWrapper/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
LstWrapper/appsettings.json
Normal file
9
LstWrapper/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
36
LstWrapper/web.config
Normal file
36
LstWrapper/web.config
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<system.webServer>
|
||||||
|
<!-- Enable WebSockets -->
|
||||||
|
<webSocket enabled="true" receiveBufferLimit="4194304" pingInterval="00:01:00" />
|
||||||
|
|
||||||
|
<rewrite>
|
||||||
|
<rules>
|
||||||
|
<!-- Proxy all requests starting with /lst/ to the .NET wrapper (port 4000) -->
|
||||||
|
<rule name="Proxy to Wrapper" stopProcessing="true">
|
||||||
|
<match url="^lst/(.*)" />
|
||||||
|
<conditions>
|
||||||
|
<!-- Skip this rule if it's a WebSocket request -->
|
||||||
|
<add input="{HTTP_UPGRADE}" pattern="^WebSocket$" negate="true" />
|
||||||
|
</conditions>
|
||||||
|
<action type="Rewrite" url="http://localhost:8080/{R:1}" />
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</rewrite>
|
||||||
|
|
||||||
|
<staticContent>
|
||||||
|
<mimeMap fileExtension=".js" mimeType="application/javascript" />
|
||||||
|
<mimeMap fileExtension=".mjs" mimeType="application/javascript" />
|
||||||
|
<mimeMap fileExtension=".css" mimeType="text/css" />
|
||||||
|
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
|
||||||
|
</staticContent>
|
||||||
|
|
||||||
|
<handlers>
|
||||||
|
<!-- Let AspNetCoreModule handle all requests -->
|
||||||
|
<remove name="WebSocketHandler" />
|
||||||
|
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
|
||||||
|
</handlers>
|
||||||
|
|
||||||
|
<aspNetCore processPath="dotnet" arguments=".\LstWrapper.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
||||||
6
LstWrapper/wwwroot/index.html
Normal file
6
LstWrapper/wwwroot/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>The new begining to lst</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -10,3 +10,5 @@ this will also include a primary server to house all the common configs across a
|
|||||||
|
|
||||||
The new lst will run in docker by building your own image and deploying or pulling the image down.
|
The new lst will run in docker by building your own image and deploying or pulling the image down.
|
||||||
you will also be able to run it in windows or linux.
|
you will also be able to run it in windows or linux.
|
||||||
|
|
||||||
|
when developing in lst and you want to run hotloads installed and configure https://github.com/air-verse/air
|
||||||
|
|||||||
0
backend/.air.toml
Normal file
0
backend/.air.toml
Normal file
2
backend/.dockerignore
Normal file
2
backend/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
lst_backend.exe
|
||||||
|
lst.net.exe
|
||||||
@@ -3,19 +3,25 @@ FROM golang:1.24.4-alpine3.22 AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
# COPY VERSION ./VERSION
|
COPY docs /app/docs/
|
||||||
|
COPY frontend /app/frontend/
|
||||||
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o lst_go ./main.go
|
RUN CGO_ENABLED=0 GOOS=linux go build -o lst_go ./main.go
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
|
|
||||||
|
# Copy only the binary (no need for source files)
|
||||||
|
RUN mkdir -p ./docs ./frontend
|
||||||
|
|
||||||
COPY --from=builder /app/lst_go .
|
COPY --from=builder /app/lst_go .
|
||||||
# COPY --from=builder /app/VERSION ./
|
COPY --from=builder /app/docs ./docs/
|
||||||
|
COPY --from=builder /app/frontend ./frontend/
|
||||||
|
|
||||||
# create the volume paths
|
# create the volume paths
|
||||||
RUN mkdir -p /data
|
RUN mkdir -p /data
|
||||||
|
|||||||
@@ -2,33 +2,52 @@ module lst.net
|
|||||||
|
|
||||||
go 1.24.3
|
go 1.24.3
|
||||||
|
|
||||||
require github.com/gin-gonic/gin v1.10.1
|
require (
|
||||||
|
github.com/bensch777/discord-webhook-golang v0.0.6
|
||||||
|
github.com/gin-contrib/cors v1.7.6
|
||||||
|
github.com/gin-gonic/gin v1.10.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/rs/zerolog v1.34.0
|
||||||
|
gorm.io/driver/postgres v1.6.0
|
||||||
|
gorm.io/gorm v1.30.1
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/bytedance/sonic v1.13.3 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.18.0 // indirect
|
||||||
golang.org/x/crypto v0.23.0 // indirect
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
golang.org/x/net v0.25.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
51
backend/internal/db/db.go
Normal file
51
backend/internal/db/db.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
type DBConfig struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
DSN string
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDB() (*DBConfig, error) {
|
||||||
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s",
|
||||||
|
os.Getenv("DB_HOST"),
|
||||||
|
os.Getenv("DB_PORT"),
|
||||||
|
os.Getenv("DB_USER"),
|
||||||
|
os.Getenv("DB_PASSWORD"),
|
||||||
|
os.Getenv("DB_NAME"))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✅ Connected to database")
|
||||||
|
|
||||||
|
// ensures we have the uuid stuff setup properly
|
||||||
|
DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`)
|
||||||
|
|
||||||
|
err = DB.AutoMigrate(&models.Log{}, &models.Settings{}) // &ClientRecord{}, &Servers{}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to auto-migrate models: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✅ Database migration completed successfully")
|
||||||
|
|
||||||
|
return &DBConfig{
|
||||||
|
DB: DB,
|
||||||
|
DSN: dsn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
21
backend/internal/models/logs.go
Normal file
21
backend/internal/models/logs.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Log struct {
|
||||||
|
LogID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
|
||||||
|
Level string `gorm:"size:10;not null"` // "info", "error", etc.
|
||||||
|
Message string `gorm:"not null"`
|
||||||
|
Service string `gorm:"size:50"`
|
||||||
|
Metadata pkg.JSONB `gorm:"type:jsonb"` // fields (e.g., {"user_id": 123})
|
||||||
|
CreatedAt time.Time `gorm:"index"`
|
||||||
|
Checked bool `gorm:"type:boolean;default:false"`
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
}
|
||||||
32
backend/internal/models/servers.go
Normal file
32
backend/internal/models/servers.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"lst.net/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Servers struct {
|
||||||
|
ServerID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
|
||||||
|
ServerName string `gorm:"size:50;not null"`
|
||||||
|
ServerDNS string `gorm:"size:25;not null"`
|
||||||
|
PlantToken string `gorm:"size:10;not null"`
|
||||||
|
IPAddress string `gorm:"size:16;not null"`
|
||||||
|
GreatPlainsPlantCode int `gorm:"size:10;not null"`
|
||||||
|
StreetAddress string `gorm:"size:255;not null"`
|
||||||
|
CityState string `gorm:"size:50;not null"`
|
||||||
|
Zipcode int `gorm:"size:13;not null"`
|
||||||
|
ContactEmail string `gorm:"size:255"`
|
||||||
|
ContactPhone string `gorm:"size:255"`
|
||||||
|
CustomerTiAcc string `gorm:"size:255"`
|
||||||
|
LstServerPort int `gorm:"size:255; not null"`
|
||||||
|
Active bool `gorm:"type:boolean;default:true"`
|
||||||
|
LerverLoc string `gorm:"size:255:not null"`
|
||||||
|
LastUpdated time.Time `gorm:"index"`
|
||||||
|
ShippingHours pkg.JSONB `gorm:"type:jsonb;default:'[{\"early\": \"06:30\", \"late\": \"23:00\"}]'"`
|
||||||
|
TiPostTime pkg.JSONB `gorm:"type:jsonb;default:'[{\"from\": \"24\", \"to\": \"24\"}]'"`
|
||||||
|
OtherSettings pkg.JSONB `gorm:"type:jsonb;default:'[{\"specialInstructions\": \"something for ti\", \"active\": false}]'"`
|
||||||
|
IsUpgrading bool `gorm:"type:boolean;default:true"`
|
||||||
|
AlplaProdApiKey string `gorm:"size:255"`
|
||||||
|
}
|
||||||
20
backend/internal/models/settings.go
Normal file
20
backend/internal/models/settings.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
SettingID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
|
||||||
|
Name string `gorm:"uniqueIndex;not null"`
|
||||||
|
Description string `gorm:"type:text"`
|
||||||
|
Value string `gorm:"not null"`
|
||||||
|
Enabled bool `gorm:"default:true"`
|
||||||
|
AppService string `gorm:"default:system"`
|
||||||
|
CreatedAt time.Time `gorm:"index"`
|
||||||
|
UpdatedAt time.Time `gorm:"index"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
}
|
||||||
21
backend/internal/models/ws_client.go
Normal file
21
backend/internal/models/ws_client.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"lst.net/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientRecord struct {
|
||||||
|
ClientID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
|
||||||
|
APIKey string `gorm:"not null"`
|
||||||
|
IPAddress string `gorm:"not null"`
|
||||||
|
UserAgent string `gorm:"size:255"`
|
||||||
|
ConnectedAt time.Time `gorm:"index"`
|
||||||
|
LastHeartbeat time.Time `gorm:"column:last_heartbeat"`
|
||||||
|
Channels pkg.JSONB `gorm:"type:jsonb"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DisconnectedAt *time.Time `gorm:"column:disconnected_at"`
|
||||||
|
}
|
||||||
179
backend/internal/notifications/ws/ws_channel_manager.go
Normal file
179
backend/internal/notifications/ws/ws_channel_manager.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Channel struct {
|
||||||
|
Name string
|
||||||
|
Clients map[*Client]bool
|
||||||
|
Register chan *Client
|
||||||
|
Unregister chan *Client
|
||||||
|
Broadcast chan []byte
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
channels = make(map[string]*Channel)
|
||||||
|
channelsMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitializeChannels creates and returns all channels
|
||||||
|
func InitializeChannels() {
|
||||||
|
channelsMu.Lock()
|
||||||
|
defer channelsMu.Unlock()
|
||||||
|
|
||||||
|
channels["logServices"] = NewChannel("logServices")
|
||||||
|
channels["labels"] = NewChannel("labels")
|
||||||
|
// Add more channels here as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChannel(name string) *Channel {
|
||||||
|
return &Channel{
|
||||||
|
Name: name,
|
||||||
|
Clients: make(map[*Client]bool),
|
||||||
|
Register: make(chan *Client),
|
||||||
|
Unregister: make(chan *Client),
|
||||||
|
Broadcast: make(chan []byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetChannel(name string) (*Channel, bool) {
|
||||||
|
channelsMu.RLock()
|
||||||
|
defer channelsMu.RUnlock()
|
||||||
|
ch, exists := channels[name]
|
||||||
|
return ch, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllChannels() map[string]*Channel {
|
||||||
|
channelsMu.RLock()
|
||||||
|
defer channelsMu.RUnlock()
|
||||||
|
|
||||||
|
chs := make(map[string]*Channel)
|
||||||
|
for k, v := range channels {
|
||||||
|
chs[k] = v
|
||||||
|
}
|
||||||
|
return chs
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartAllChannels() {
|
||||||
|
|
||||||
|
channelsMu.RLock()
|
||||||
|
defer channelsMu.RUnlock()
|
||||||
|
|
||||||
|
for _, ch := range channels {
|
||||||
|
go ch.RunChannel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CleanupChannels() {
|
||||||
|
channelsMu.Lock()
|
||||||
|
defer channelsMu.Unlock()
|
||||||
|
|
||||||
|
for _, ch := range channels {
|
||||||
|
close(ch.Broadcast)
|
||||||
|
// Add any other cleanup needed
|
||||||
|
}
|
||||||
|
channels = make(map[string]*Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartBroadcasting(broadcaster chan logger.Message, channels map[string]*Channel) {
|
||||||
|
logger := logger.New()
|
||||||
|
go func() {
|
||||||
|
for msg := range broadcaster {
|
||||||
|
switch msg.Channel {
|
||||||
|
case "logServices":
|
||||||
|
// Just forward the message - filtering happens in RunChannel()
|
||||||
|
messageBytes, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error marshaling message", "websocket", map[string]interface{}{
|
||||||
|
"errors": err,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
channels["logServices"].Broadcast <- messageBytes
|
||||||
|
|
||||||
|
case "labels":
|
||||||
|
// Future labels handling
|
||||||
|
messageBytes, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error marshaling message", "websocket", map[string]interface{}{
|
||||||
|
"errors": err,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
channels["labels"].Broadcast <- messageBytes
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Printf("Received message for unknown channel: %s", msg.Channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(slice []string, item string) bool {
|
||||||
|
// Empty filter slice means "match all"
|
||||||
|
if len(slice) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case-insensitive comparison
|
||||||
|
item = strings.ToLower(item)
|
||||||
|
for _, s := range slice {
|
||||||
|
if strings.ToLower(s) == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updated Channel.RunChannel() for logServices filtering
|
||||||
|
func (ch *Channel) RunChannel() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case client := <-ch.Register:
|
||||||
|
ch.lock.Lock()
|
||||||
|
ch.Clients[client] = true
|
||||||
|
ch.lock.Unlock()
|
||||||
|
|
||||||
|
case client := <-ch.Unregister:
|
||||||
|
ch.lock.Lock()
|
||||||
|
delete(ch.Clients, client)
|
||||||
|
ch.lock.Unlock()
|
||||||
|
|
||||||
|
case message := <-ch.Broadcast:
|
||||||
|
var msg logger.Message
|
||||||
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ch.lock.RLock()
|
||||||
|
for client := range ch.Clients {
|
||||||
|
// Special filtering for logServices
|
||||||
|
if ch.Name == "logServices" {
|
||||||
|
logLevel, _ := msg.Meta["level"].(string)
|
||||||
|
logService, _ := msg.Meta["service"].(string)
|
||||||
|
|
||||||
|
levelMatch := len(client.LogLevels) == 0 || contains(client.LogLevels, logLevel)
|
||||||
|
serviceMatch := len(client.Services) == 0 || contains(client.Services, logService)
|
||||||
|
|
||||||
|
if !levelMatch || !serviceMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case client.Send <- message:
|
||||||
|
default:
|
||||||
|
ch.Unregister <- client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch.lock.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
292
backend/internal/notifications/ws/ws_client.go
Normal file
292
backend/internal/notifications/ws/ws_client.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clients = make(map[*Client]bool)
|
||||||
|
clientsMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
ClientID uuid.UUID `json:"client_id"`
|
||||||
|
Conn *websocket.Conn `json:"-"` // Excluded from JSON
|
||||||
|
APIKey string `json:"api_key"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
Send chan []byte `json:"-"` // Excluded from JSON
|
||||||
|
Channels map[string]bool `json:"channels"`
|
||||||
|
LogLevels []string `json:"levels,omitempty"`
|
||||||
|
Services []string `json:"services,omitempty"`
|
||||||
|
Labels []string `json:"labels,omitempty"`
|
||||||
|
ConnectedAt time.Time `json:"connected_at"`
|
||||||
|
done chan struct{} // For graceful shutdown
|
||||||
|
isAlive atomic.Bool
|
||||||
|
lastActive time.Time // Tracks last activity
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SaveToDB(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
|
// Convert c.Channels (map[string]bool) to map[string]interface{} for JSONB
|
||||||
|
channels := make(map[string]interface{})
|
||||||
|
for ch := range c.Channels {
|
||||||
|
channels[ch] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
clientRecord := &models.ClientRecord{
|
||||||
|
APIKey: c.APIKey,
|
||||||
|
IPAddress: c.IPAddress,
|
||||||
|
UserAgent: c.UserAgent,
|
||||||
|
Channels: pkg.JSONB(channels),
|
||||||
|
ConnectedAt: time.Now(),
|
||||||
|
LastHeartbeat: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Create(&clientRecord).Error; err != nil {
|
||||||
|
log.Error("❌ Error saving client", "websocket", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
c.ClientID = clientRecord.ClientID
|
||||||
|
c.ConnectedAt = clientRecord.ConnectedAt
|
||||||
|
|
||||||
|
clientData := fmt.Sprintf("A new client %v, just connected", c.ClientID)
|
||||||
|
log.Info(clientData, "websocket", map[string]interface{}{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MarkDisconnected(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
|
clientData := fmt.Sprintf("Client %v Dicconected", c.ClientID)
|
||||||
|
log.Info(clientData, "websocket", map[string]interface{}{})
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
res := db.Model(&models.ClientRecord{}).
|
||||||
|
Where("client_id = ?", c.ClientID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"disconnected_at": &now,
|
||||||
|
})
|
||||||
|
|
||||||
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
|
log.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if res.Error != nil {
|
||||||
|
|
||||||
|
log.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": res.Error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SafeClient() *Client {
|
||||||
|
return &Client{
|
||||||
|
ClientID: c.ClientID,
|
||||||
|
APIKey: c.APIKey,
|
||||||
|
IPAddress: c.IPAddress,
|
||||||
|
UserAgent: c.UserAgent,
|
||||||
|
Channels: c.Channels,
|
||||||
|
LogLevels: c.LogLevels,
|
||||||
|
Services: c.Services,
|
||||||
|
Labels: c.Labels,
|
||||||
|
ConnectedAt: c.ConnectedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllClients returns safe representations of all clients
|
||||||
|
func GetAllClients() []*Client {
|
||||||
|
clientsMu.RLock()
|
||||||
|
defer clientsMu.RUnlock()
|
||||||
|
|
||||||
|
var clientList []*Client
|
||||||
|
for client := range clients {
|
||||||
|
clientList = append(clientList, client.SafeClient())
|
||||||
|
}
|
||||||
|
return clientList
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientsByChannel returns clients in a specific channel
|
||||||
|
func GetClientsByChannel(channel string) []*Client {
|
||||||
|
clientsMu.RLock()
|
||||||
|
defer clientsMu.RUnlock()
|
||||||
|
|
||||||
|
var channelClients []*Client
|
||||||
|
for client := range clients {
|
||||||
|
if client.Channels[channel] {
|
||||||
|
channelClients = append(channelClients, client.SafeClient())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return channelClients
|
||||||
|
}
|
||||||
|
|
||||||
|
// heat beat stuff
|
||||||
|
const (
|
||||||
|
pingPeriod = 30 * time.Second
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) StartHeartbeat(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
|
log.Debug("Started hearbeat", "websocket", map[string]interface{}{})
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if !c.isAlive.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
log.Error("Heartbeat failed", "websocket", map[string]interface{}{
|
||||||
|
"client_id": c.ClientID,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
c.Close(log, db)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
res := db.Model(&models.ClientRecord{}).
|
||||||
|
Where("client_id = ?", c.ClientID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"last_heartbeat": &now,
|
||||||
|
})
|
||||||
|
|
||||||
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
|
log.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if res.Error != nil {
|
||||||
|
|
||||||
|
log.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": res.Error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
clientStuff := fmt.Sprintf("HeartBeat just done on: %v", c.ClientID)
|
||||||
|
log.Info(clientStuff, "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
|
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
if c.isAlive.CompareAndSwap(true, false) { // Atomic swap
|
||||||
|
close(c.done)
|
||||||
|
c.Conn.Close()
|
||||||
|
// Add any other cleanup here
|
||||||
|
c.MarkDisconnected(log, db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) startServerPings(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
ticker := time.NewTicker(60 * time.Second) // Ping every 30s
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
|
||||||
|
log.Error("Server Ping failed", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.Close(log, db)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) markActive() {
|
||||||
|
c.lastActive = time.Now() // No mutex needed if single-writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) IsActive() bool {
|
||||||
|
return time.Since(c.lastActive) < 45*time.Second // 1.5x ping interval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) updateHeartbeat(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
//fmt.Println("Updating heatbeat")
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
//fmt.Printf("Updating heartbeat for client: %s at %v\n", c.ClientID, now)
|
||||||
|
|
||||||
|
//db.DB = db.DB.Debug()
|
||||||
|
res := db.Model(&models.ClientRecord{}).
|
||||||
|
Where("client_id = ?", c.ClientID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"last_heartbeat": &now, // Explicit format
|
||||||
|
})
|
||||||
|
//fmt.Printf("Executed SQL: %v\n", db.DB.Statement.SQL.String())
|
||||||
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
|
log.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if res.Error != nil {
|
||||||
|
|
||||||
|
log.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": res.Error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 2. Verify DB connection
|
||||||
|
if db == nil {
|
||||||
|
log.Error("DB connection is nil", "websocket", map[string]interface{}{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Test raw SQL execution first
|
||||||
|
testRes := db.Exec("SELECT 1")
|
||||||
|
if testRes.Error != nil {
|
||||||
|
log.Error("DB ping failed", "websocket", map[string]interface{}{
|
||||||
|
"error": testRes.Error,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// work on this stats later
|
||||||
|
// Add to your admin endpoint
|
||||||
|
// type ConnectionStats struct {
|
||||||
|
// TotalConnections int `json:"total_connections"`
|
||||||
|
// ActiveConnections int `json:"active_connections"`
|
||||||
|
// AvgDuration string `json:"avg_duration"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func GetConnectionStats() ConnectionStats {
|
||||||
|
// // Implement your metrics tracking
|
||||||
|
// }
|
||||||
229
backend/internal/notifications/ws/ws_handler.go
Normal file
229
backend/internal/notifications/ws/ws_handler.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JoinPayload struct {
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
APIKey string `json:"apiKey"`
|
||||||
|
Services []string `json:"services,omitempty"`
|
||||||
|
Levels []string `json:"levels,omitempty"`
|
||||||
|
Labels []string `json:"labels,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins; customize for prod
|
||||||
|
HandshakeTimeout: 15 * time.Second,
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
EnableCompression: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func SocketHandler(c *gin.Context, channels map[string]*Channel, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
// Upgrade HTTP to WebSocket
|
||||||
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("WebSocket upgrade failed", "websocket", map[string]interface{}{"error": err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//defer conn.Close()
|
||||||
|
|
||||||
|
// Create new client
|
||||||
|
client := &Client{
|
||||||
|
Conn: conn,
|
||||||
|
APIKey: "exampleAPIKey",
|
||||||
|
Send: make(chan []byte, 256), // Buffered channel
|
||||||
|
Channels: make(map[string]bool),
|
||||||
|
IPAddress: c.ClientIP(),
|
||||||
|
UserAgent: c.Request.UserAgent(),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.isAlive.Store(true)
|
||||||
|
// Add to global clients map
|
||||||
|
clientsMu.Lock()
|
||||||
|
clients[client] = true
|
||||||
|
clientsMu.Unlock()
|
||||||
|
|
||||||
|
// Save initial connection to DB
|
||||||
|
client.SaveToDB(log, db)
|
||||||
|
// Save initial connection to DB
|
||||||
|
// if err := client.SaveToDB(); err != nil {
|
||||||
|
// log.Println("Failed to save client to DB:", err)
|
||||||
|
// conn.Close()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Set handlers
|
||||||
|
conn.SetPingHandler(func(string) error {
|
||||||
|
return nil // Auto-responds with pong
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.SetPongHandler(func(string) error {
|
||||||
|
now := time.Now()
|
||||||
|
client.markActive() // Track last pong time
|
||||||
|
client.lastActive = now
|
||||||
|
client.updateHeartbeat(log, db)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start server-side ping ticker
|
||||||
|
go client.startServerPings(log, db)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// Unregister from all channels
|
||||||
|
for channelName := range client.Channels {
|
||||||
|
if ch, exists := channels[channelName]; exists {
|
||||||
|
ch.Unregister <- client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from global clients map
|
||||||
|
clientsMu.Lock()
|
||||||
|
delete(clients, client)
|
||||||
|
clientsMu.Unlock()
|
||||||
|
|
||||||
|
// Mark disconnected in DB
|
||||||
|
client.MarkDisconnected(log, db)
|
||||||
|
|
||||||
|
// Close connection
|
||||||
|
conn.Close()
|
||||||
|
log.Info("Client disconnected", "websocket", map[string]interface{}{
|
||||||
|
"client": client.ClientID,
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Send welcome message immediately
|
||||||
|
welcomeMsg := map[string]string{
|
||||||
|
"status": "connected",
|
||||||
|
"message": "Welcome to the WebSocket server. Send subscription request to begin.",
|
||||||
|
}
|
||||||
|
if err := conn.WriteJSON(welcomeMsg); err != nil {
|
||||||
|
log.Error("Failed to send welcome message", "websocket", map[string]interface{}{"error": err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message handling goroutine
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
// Cleanup on disconnect
|
||||||
|
for channelName := range client.Channels {
|
||||||
|
if ch, exists := channels[channelName]; exists {
|
||||||
|
ch.Unregister <- client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(client.Send)
|
||||||
|
client.MarkDisconnected(log, db)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, msg, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||||
|
log.Error("Client disconnected unexpectedl", "websocket", map[string]interface{}{"error": err})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload struct {
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
APIKey string `json:"apiKey"`
|
||||||
|
Services []string `json:"services,omitempty"`
|
||||||
|
Levels []string `json:"levels,omitempty"`
|
||||||
|
Labels []string `json:"labels,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(msg, &payload); err != nil {
|
||||||
|
conn.WriteJSON(map[string]string{"error": "invalid payload format"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate API key (implement your own validateAPIKey function)
|
||||||
|
// if payload.APIKey == "" || !validateAPIKey(payload.APIKey) {
|
||||||
|
// conn.WriteJSON(map[string]string{"error": "invalid or missing API key"})
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
if payload.APIKey == "" {
|
||||||
|
conn.WriteMessage(websocket.TextMessage, []byte("Missing API Key"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client.APIKey = payload.APIKey
|
||||||
|
|
||||||
|
// Handle channel subscription
|
||||||
|
switch payload.Channel {
|
||||||
|
case "logServices":
|
||||||
|
// Unregister from other channels if needed
|
||||||
|
if client.Channels["labels"] {
|
||||||
|
channels["labels"].Unregister <- client
|
||||||
|
delete(client.Channels, "labels")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update client filters
|
||||||
|
client.Services = payload.Services
|
||||||
|
client.LogLevels = payload.Levels
|
||||||
|
|
||||||
|
// Register to channel
|
||||||
|
channels["logServices"].Register <- client
|
||||||
|
client.Channels["logServices"] = true
|
||||||
|
|
||||||
|
conn.WriteJSON(map[string]string{
|
||||||
|
"message": "You are now subscribed to the the service channel",
|
||||||
|
"status": "subscribed",
|
||||||
|
"channel": "logServices",
|
||||||
|
})
|
||||||
|
|
||||||
|
case "labels":
|
||||||
|
// Unregister from other channels if needed
|
||||||
|
if client.Channels["logServices"] {
|
||||||
|
channels["logServices"].Unregister <- client
|
||||||
|
delete(client.Channels, "logServices")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set label filters if provided
|
||||||
|
if payload.Labels != nil {
|
||||||
|
client.Labels = payload.Labels
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register to channel
|
||||||
|
channels["labels"].Register <- client
|
||||||
|
client.Channels["labels"] = true
|
||||||
|
|
||||||
|
// Update DB record
|
||||||
|
client.SaveToDB(log, db)
|
||||||
|
// if err := client.SaveToDB(); err != nil {
|
||||||
|
// log.Println("Failed to update client labels:", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
conn.WriteJSON(map[string]interface{}{
|
||||||
|
"message": "You are now subscribed to the label channel",
|
||||||
|
"status": "subscribed",
|
||||||
|
"channel": "labels",
|
||||||
|
"filters": client.Labels,
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
conn.WriteJSON(map[string]string{
|
||||||
|
"error": "invalid channel",
|
||||||
|
"available_channels": "logServices, labels",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Send messages to client
|
||||||
|
for message := range client.Send {
|
||||||
|
if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||||
|
log.Error("Write erro", "websocket", map[string]interface{}{"error": err})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
backend/internal/notifications/ws/ws_log_service.go
Normal file
79
backend/internal/notifications/ws/ws_log_service.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package ws
|
||||||
|
|
||||||
|
// setup the notifiyer
|
||||||
|
|
||||||
|
// -- Only needs to be run once in DB
|
||||||
|
// CREATE OR REPLACE FUNCTION notify_new_log() RETURNS trigger AS $$
|
||||||
|
// BEGIN
|
||||||
|
// PERFORM pg_notify('new_log', row_to_json(NEW)::text);
|
||||||
|
// RETURN NEW;
|
||||||
|
// END;
|
||||||
|
// $$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
// DROP TRIGGER IF EXISTS new_log_trigger ON logs;
|
||||||
|
|
||||||
|
// CREATE TRIGGER new_log_trigger
|
||||||
|
// AFTER INSERT ON logs
|
||||||
|
// FOR EACH ROW EXECUTE FUNCTION notify_new_log();
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogServices(broadcaster chan logger.Message, log *logger.CustomLogger) {
|
||||||
|
|
||||||
|
log.Info("[LogServices] started - single channel for all logs", "websocket", map[string]interface{}{})
|
||||||
|
|
||||||
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||||
|
os.Getenv("DB_HOST"),
|
||||||
|
os.Getenv("DB_PORT"),
|
||||||
|
os.Getenv("DB_USER"),
|
||||||
|
os.Getenv("DB_PASSWORD"),
|
||||||
|
os.Getenv("DB_NAME"),
|
||||||
|
)
|
||||||
|
|
||||||
|
listener := pq.NewListener(dsn, 10*time.Second, time.Minute, nil)
|
||||||
|
err := listener.Listen("new_log")
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("Failed to LISTEN on new_log", "logger", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Listening for all logs through single logServices channel...", "wbsocker", map[string]interface{}{})
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case notify := <-listener.Notify:
|
||||||
|
if notify != nil {
|
||||||
|
var logData map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(notify.Extra), &logData); err != nil {
|
||||||
|
log.Error("Failed to unmarshal notification payload", "logger", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always send to logServices channel
|
||||||
|
broadcaster <- logger.Message{
|
||||||
|
Channel: "logServices",
|
||||||
|
Data: logData,
|
||||||
|
Meta: map[string]interface{}{
|
||||||
|
"level": logData["level"],
|
||||||
|
"service": logData["service"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(90 * time.Second):
|
||||||
|
go func() {
|
||||||
|
listener.Ping()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
backend/internal/notifications/ws/ws_routes.go
Normal file
56
backend/internal/notifications/ws/ws_routes.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
broadcaster = make(chan logger.Message)
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterSocketRoutes(r *gin.Engine, base_url string, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
// Initialize all channels
|
||||||
|
InitializeChannels()
|
||||||
|
|
||||||
|
// Start channel processors
|
||||||
|
StartAllChannels()
|
||||||
|
|
||||||
|
// Start background services
|
||||||
|
go LogServices(broadcaster, log)
|
||||||
|
go StartBroadcasting(broadcaster, channels)
|
||||||
|
|
||||||
|
// WebSocket route
|
||||||
|
r.GET(base_url+"/ws", func(c *gin.Context) {
|
||||||
|
SocketHandler(c, channels, log, db)
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET(base_url+"/ws/clients", AdminAuthMiddleware(), handleGetClients)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetClients(c *gin.Context) {
|
||||||
|
channel := c.Query("channel")
|
||||||
|
|
||||||
|
var clientList []*Client
|
||||||
|
if channel != "" {
|
||||||
|
clientList = GetClientsByChannel(channel)
|
||||||
|
} else {
|
||||||
|
clientList = GetAllClients()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"count": len(clientList),
|
||||||
|
"clients": clientList,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminAuthMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Implement your admin authentication logic
|
||||||
|
// Example: Check API key or JWT token
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
41
backend/internal/router/middleware/settings_Check.go
Normal file
41
backend/internal/router/middleware/settings_Check.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"lst.net/internal/system/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SettingCheckMiddleware(settingName string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Debug: Log the setting name we're checking
|
||||||
|
//log.Printf("Checking setting '%s' for path: %s", settingName, c.Request.URL.Path)
|
||||||
|
|
||||||
|
// Get the current setting value
|
||||||
|
value, err := settings.GetString(settingName)
|
||||||
|
if err != nil {
|
||||||
|
//log.Printf("Error getting setting '%s': %v", settingName, err)
|
||||||
|
c.AbortWithStatusJSON(404, gin.H{
|
||||||
|
"error": "endpoint not available",
|
||||||
|
"details": "setting error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Log the actual value received
|
||||||
|
//log.Printf("Setting '%s' value: '%s'", settingName, value)
|
||||||
|
|
||||||
|
// Changed condition to check for "1" (enable) instead of "0" (disable)
|
||||||
|
if value != "1" {
|
||||||
|
//log.Printf("Setting '%s' not enabled (value: '%s')", settingName, value)
|
||||||
|
c.AbortWithStatusJSON(404, gin.H{
|
||||||
|
"error": "endpoint not available",
|
||||||
|
"details": "required feature is disabled",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Log successful check
|
||||||
|
//log.Printf("Setting check passed for '%s'", settingName)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
66
backend/internal/router/router.go
Normal file
66
backend/internal/router/router.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/notifications/ws"
|
||||||
|
"lst.net/internal/router/middleware"
|
||||||
|
"lst.net/internal/system/servers"
|
||||||
|
"lst.net/internal/system/settings"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Setup(db *gorm.DB, basePath string, log *logger.CustomLogger) *gin.Engine {
|
||||||
|
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
if os.Getenv("APP_ENV") == "production" {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable CORS (adjust origins as needed)
|
||||||
|
r.Use(cors.New(cors.Config{
|
||||||
|
AllowOrigins: []string{"*"}, // Allow all origins (change in production)
|
||||||
|
AllowMethods: []string{"GET", "OPTIONS", "POST", "DELETE", "PATCH", "CONNECT"},
|
||||||
|
AllowHeaders: []string{"Origin", "Cache-Control", "Content-Type"},
|
||||||
|
ExposeHeaders: []string{"Content-Length"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
AllowWebSockets: true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Serve Docusaurus static files
|
||||||
|
r.StaticFS(basePath+"/docs", http.Dir("docs"))
|
||||||
|
r.StaticFS(basePath+"/app", http.Dir("frontend"))
|
||||||
|
|
||||||
|
// all routes to there respective systems.
|
||||||
|
ws.RegisterSocketRoutes(r, basePath, log, db)
|
||||||
|
settings.RegisterSettingsRoutes(r, basePath, log, db)
|
||||||
|
servers.RegisterServersRoutes(r, basePath, log, db)
|
||||||
|
|
||||||
|
r.GET(basePath+"/api/ping", middleware.SettingCheckMiddleware("testingApiFunction"), func(c *gin.Context) {
|
||||||
|
log.Info("Checking if the server is up", "system", map[string]interface{}{
|
||||||
|
"endpoint": "/api/ping",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
})
|
||||||
|
c.JSON(200, gin.H{"message": "pong"})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Any(basePath+"/", func(c *gin.Context) { errorApiLoc(c, log) })
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorApiLoc(c *gin.Context, log *logger.CustomLogger) {
|
||||||
|
|
||||||
|
log.Error("Api endpoint hit that dose not exist", "system", map[string]interface{}{
|
||||||
|
"endpoint": c.Request.URL.Path,
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
})
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"message": "looks like you have encountered a route that dose not exist"})
|
||||||
|
}
|
||||||
65
backend/internal/system/servers/get_servers.go
Normal file
65
backend/internal/system/servers/get_servers.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package servers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getServers(c *gin.Context, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
|
servers, err := GetServers(log, db)
|
||||||
|
log.Info("Current Settings", "system", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
log.Error("Current Settings", "system", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
c.JSON(500, gin.H{"message": "There was an error getting the settings", "error": err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"message": "Current settings", "data": servers})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetServers(log *logger.CustomLogger, db *gorm.DB) ([]map[string]interface{}, error) {
|
||||||
|
var servers []models.Servers
|
||||||
|
res := db.Find(&servers)
|
||||||
|
|
||||||
|
if res.Error != nil {
|
||||||
|
return nil, res.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
toLowercase := func(s models.Servers) map[string]interface{} {
|
||||||
|
t := reflect.TypeOf(s)
|
||||||
|
v := reflect.ValueOf(s)
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := strings.ToLower(t.Field(i).Name)
|
||||||
|
data[field] = v.Field(i).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
var lowercaseServers []map[string]interface{}
|
||||||
|
for _, server := range servers {
|
||||||
|
lowercaseServers = append(lowercaseServers, toLowercase(server))
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowercaseServers, nil
|
||||||
|
}
|
||||||
21
backend/internal/system/servers/new_server.go
Normal file
21
backend/internal/system/servers/new_server.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package servers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewServer(serverData models.Servers, log *logger.CustomLogger, db *gorm.DB) (string, error) {
|
||||||
|
|
||||||
|
err := db.Create(&serverData).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("There was an error adding the new server", "server", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return "There was an error adding the new server", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "New server was just created", nil
|
||||||
|
}
|
||||||
13
backend/internal/system/servers/servers.go
Normal file
13
backend/internal/system/servers/servers.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package servers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterServersRoutes(l *gin.Engine, baseUrl string, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
|
s := l.Group(baseUrl + "/api/v1")
|
||||||
|
s.GET("/servers", func(c *gin.Context) { getServers(c, log, db) })
|
||||||
|
}
|
||||||
59
backend/internal/system/servers/update_server.go
Normal file
59
backend/internal/system/servers/update_server.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package servers
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "encoding/json"
|
||||||
|
|
||||||
|
// "github.com/gin-gonic/gin"
|
||||||
|
// "lst.net/internal/db"
|
||||||
|
// "lst.net/pkg/logger"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// func updateSettingById(c *gin.Context) {
|
||||||
|
// log := logger.New()
|
||||||
|
// settingID := c.Param("id")
|
||||||
|
|
||||||
|
// if settingID == "" {
|
||||||
|
// c.JSON(500, gin.H{"message": "Invalid data"})
|
||||||
|
// log.Error("Invalid data", "system", map[string]interface{}{
|
||||||
|
// "endpoint": "/api/v1/settings",
|
||||||
|
// "client_ip": c.ClientIP(),
|
||||||
|
// "user_agent": c.Request.UserAgent(),
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// var setting SettingUpdateInput
|
||||||
|
|
||||||
|
// //err := c.ShouldBindBodyWithJSON(&setting)
|
||||||
|
|
||||||
|
// decoder := json.NewDecoder(c.Request.Body) // more strict and will force us to have correct data
|
||||||
|
// decoder.DisallowUnknownFields()
|
||||||
|
|
||||||
|
// if err := decoder.Decode(&setting); err != nil {
|
||||||
|
// c.JSON(400, gin.H{"message": "Invalid request body", "error": err.Error()})
|
||||||
|
// log.Error("Invalid request body", "system", map[string]interface{}{
|
||||||
|
// "endpoint": "/api/v1/settings",
|
||||||
|
// "client_ip": c.ClientIP(),
|
||||||
|
// "user_agent": c.Request.UserAgent(),
|
||||||
|
// "error": err,
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := UpdateServer(db.DB, settingID, setting); err != nil {
|
||||||
|
// c.JSON(500, gin.H{"message": "Failed to update setting", "error": err.Error()})
|
||||||
|
// log.Error("Failed to update setting", "system", map[string]interface{}{
|
||||||
|
// "endpoint": "/api/v1/settings",
|
||||||
|
// "client_ip": c.ClientIP(),
|
||||||
|
// "user_agent": c.Request.UserAgent(),
|
||||||
|
// "error": err,
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c.JSON(200, gin.H{"message": "Setting was just updated", "data": setting})
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func UpdateServer() (string, error) {
|
||||||
|
// return "Server was just updated", nil
|
||||||
|
// }
|
||||||
39
backend/internal/system/settings/get_settings.go
Normal file
39
backend/internal/system/settings/get_settings.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAllSettings(db *gorm.DB) ([]map[string]interface{}, error) {
|
||||||
|
// var settings []models.Settings
|
||||||
|
// result := db.Find(&settings)
|
||||||
|
|
||||||
|
// if result.Error != nil {
|
||||||
|
// return nil, result.Error
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Function to convert struct to map with lowercase keys
|
||||||
|
// toLowercase := func(s models.Settings) map[string]interface{} {
|
||||||
|
// t := reflect.TypeOf(s)
|
||||||
|
// v := reflect.ValueOf(s)
|
||||||
|
|
||||||
|
// data := make(map[string]interface{})
|
||||||
|
|
||||||
|
// for i := 0; i < t.NumField(); i++ {
|
||||||
|
// field := strings.ToLower(t.Field(i).Name)
|
||||||
|
// data[field] = v.Field(i).Interface()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return data
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Convert each struct in settings slice to a map with lowercase keys
|
||||||
|
// var lowercaseSettings []map[string]interface{}
|
||||||
|
// for _, setting := range settings {
|
||||||
|
// lowercaseSettings = append(lowercaseSettings, toLowercase(setting))
|
||||||
|
// }
|
||||||
|
|
||||||
|
convertedSettings := GetMap()
|
||||||
|
|
||||||
|
return convertedSettings, nil
|
||||||
|
}
|
||||||
8
backend/internal/system/settings/inputs.go
Normal file
8
backend/internal/system/settings/inputs.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
type SettingUpdateInput struct {
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Value *string `json:"value"`
|
||||||
|
Enabled *bool `json:"enabled"`
|
||||||
|
AppService *string `json:"app_service"`
|
||||||
|
}
|
||||||
88
backend/internal/system/settings/settings.go
Normal file
88
backend/internal/system/settings/settings.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterSettingsRoutes(l *gin.Engine, baseUrl string, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
// seed the db on start up
|
||||||
|
SeedSettings(db, log)
|
||||||
|
|
||||||
|
s := l.Group(baseUrl + "/api/v1")
|
||||||
|
s.GET("/settings", func(c *gin.Context) { getSettings(c, log, db) })
|
||||||
|
s.PATCH("/settings/:id", func(c *gin.Context) { updateSettingById(c, log, db) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSettings(c *gin.Context, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
configs, err := GetAllSettings(db)
|
||||||
|
log.Info("Current Settings", "settings", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log := logger.New()
|
||||||
|
log.Error("Current Settings", "settings", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
c.JSON(500, gin.H{"message": "There was an error getting the settings", "error": err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"message": "Current settings", "data": configs})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSettingById(c *gin.Context, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
|
settingID := c.Param("id")
|
||||||
|
|
||||||
|
if settingID == "" {
|
||||||
|
c.JSON(500, gin.H{"message": "Invalid data"})
|
||||||
|
log.Error("Invalid data", "settings", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var setting SettingUpdateInput
|
||||||
|
|
||||||
|
//err := c.ShouldBindBodyWithJSON(&setting)
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(c.Request.Body) // more strict and will force us to have correct data
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
|
||||||
|
if err := decoder.Decode(&setting); err != nil {
|
||||||
|
c.JSON(400, gin.H{"message": "Invalid request body", "error": err.Error()})
|
||||||
|
log.Error("Invalid request body", "settings", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := UpdateSetting(log, db, settingID, setting); err != nil {
|
||||||
|
c.JSON(500, gin.H{"message": "Failed to update setting", "error": err.Error()})
|
||||||
|
log.Error("Failed to update setting", "settings", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"message": "Setting was just updated", "data": setting})
|
||||||
|
|
||||||
|
}
|
||||||
128
backend/internal/system/settings/settings_seed.go
Normal file
128
backend/internal/system/settings/settings_seed.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var seedConfigData = []models.Settings{
|
||||||
|
{Name: "serverPort", Description: "The port the server will listen on if not running in docker", Value: "4000", Enabled: true, AppService: "server"},
|
||||||
|
{Name: "server", Description: "The server we will use when connecting to the alplaprod sql", Value: "usmcd1vms006", Enabled: true, AppService: "server"},
|
||||||
|
{Name: "timezone", Value: "America/Chicago", Description: "What time zone is the server in this is used for cronjobs and some other time stuff", AppService: "server", Enabled: true},
|
||||||
|
{Name: "dbUser", Value: "alplaprod", Description: "What is the db userName", AppService: "server", Enabled: true},
|
||||||
|
{Name: "dbPass", Value: "b2JlbGl4", Description: "What is the db password", AppService: "server", Enabled: true},
|
||||||
|
{Name: "tcpPort", Value: "2222", Description: "TCP port for printers to connect send data and the zedra cameras", AppService: "server", Enabled: true},
|
||||||
|
{Name: "prolinkCheck", Value: "1", Description: "Will prolink be considered to check if matches, maninly used in plants that do not fully utilize prolink + ocp", AppService: "production", Enabled: true},
|
||||||
|
{Name: "bookin", Value: "1", Description: "do we want to book in after a label is printed", AppService: "ocp", Enabled: true},
|
||||||
|
{Name: "dbServer", Value: "usmcd1vms036", Description: "What server is the prod db on?", AppService: "server", Enabled: true},
|
||||||
|
{Name: "printDelay", Value: "90", Description: "How long in seconds between prints", AppService: "ocp", Enabled: true},
|
||||||
|
{Name: "plantToken", Value: "test3", Description: "What is the plant token", AppService: "server", Enabled: true},
|
||||||
|
{Name: "dualPrinting", Value: "0", Description: "Dose the plant have 2 machines that go to 1?", AppService: "ocp", Enabled: true},
|
||||||
|
{Name: "ocmeService", Value: "0", Description: "Is the ocme service enabled. this is gernerally only for Dayton.", AppService: "ocme", Enabled: true},
|
||||||
|
{Name: "fifoCheck", Value: "45", Description: "How far back do we want to check for fifo default 45, putting 0 will ignore.", AppService: "ocme", Enabled: true},
|
||||||
|
{Name: "dayCheck", Value: "3", Description: "how many days +/- to check for shipments in alplaprod", AppService: "ocme", Enabled: true},
|
||||||
|
{Name: "maxLotPerTruck", Value: "3", Description: "How mant lots can we have per truck?", AppService: "ocme", Enabled: true},
|
||||||
|
{Name: "monitorAddress", Value: "8", Description: "What address is monitored to be limited to the amount of lots that can be added to a truck.", AppService: "ocme", Enabled: true},
|
||||||
|
{Name: "ocmeCycleCount", Value: "1", Description: "Are we allowing ocme cycle counts?", AppService: "ocme", Enabled: true},
|
||||||
|
{Name: "devDir", Value: "", Description: "This is the dev dir and strictly only for updating the servers.", AppService: "server", Enabled: true},
|
||||||
|
{Name: "demandMGTActivated", Value: "0", Description: "Do we allow for new fake edi?", AppService: "logistics", Enabled: true},
|
||||||
|
{Name: "qualityRequest", Value: "0", Description: "quality request module?", AppService: "quality", Enabled: true},
|
||||||
|
{Name: "ocpLogsCheck", Value: "4", Description: "How long do we want to allow logs to show that have not been cleared?", AppService: "ocp", Enabled: true},
|
||||||
|
{Name: "inhouseDelivery", Value: "0", Description: "Are we doing auto inhouse delivery?", AppService: "ocp", Enabled: true},
|
||||||
|
// dyco settings
|
||||||
|
{Name: "dycoConnect", Value: "0", Description: "Are we running the dyco system?", AppService: "dycp", Enabled: true},
|
||||||
|
{Name: "dycoPrint", Value: "0", Description: "Are we using the dyco to get the labels or the rfid?", AppService: "dyco", Enabled: true},
|
||||||
|
{Name: "strapperCheck", Value: "1", Description: "Are we monitoring the strapper for faults?", AppService: "dyco", Enabled: true},
|
||||||
|
// ocp
|
||||||
|
{Name: "ocpActive", Value: `1`, Description: "Are we pritning on demand?", AppService: "ocp", Enabled: true},
|
||||||
|
{Name: "ocpCycleDelay", Value: `10`, Description: "How long between printer cycles do we want to monitor.", AppService: "ocp", Enabled: true},
|
||||||
|
{Name: "pNgAddress", Value: `139`, Description: "What is the address for p&g so we can make sure we have the correct fake edi forcast going in.", AppService: "logisitcs", Enabled: true},
|
||||||
|
{Name: "scannerID", Value: `500`, Description: "What scanner id will we be using for the app", AppService: "logistics", Enabled: true},
|
||||||
|
{Name: "scannerPort", Value: `50002`, Description: "What port instance will we be using?", AppService: "logistics", Enabled: true},
|
||||||
|
{Name: "stagingReturnLocations", Value: `30125,31523`, Description: "What are the staging location IDs we will use to select from. seperated by commas", AppService: "logistics", Enabled: true},
|
||||||
|
{Name: "testingApiFunction", Value: `1`, Description: "This is a test to validate if we set to 0 it will actaully not allow the route", AppService: "logistics", Enabled: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func SeedSettings(db *gorm.DB, log *logger.CustomLogger) error {
|
||||||
|
|
||||||
|
for _, cfg := range seedConfigData {
|
||||||
|
var existing models.Settings
|
||||||
|
if err := db.Unscoped().Where("name = ?", cfg.Name).First(&existing).Error; err == nil {
|
||||||
|
|
||||||
|
if existing.DeletedAt.Valid {
|
||||||
|
// Undelete by setting DeletedAt to NULL
|
||||||
|
if err := db.Unscoped().Model(&existing).Update("DeletedAt", gorm.DeletedAt{}).Error; err != nil {
|
||||||
|
log.Error("Failed to undelete settings", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if err := db.Create(&cfg).Error; err != nil {
|
||||||
|
log.Error("Failed to seed settings", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Try to find config by unique Name
|
||||||
|
// result := db.Where("Name =?", cfg.Name).First(&existing)
|
||||||
|
|
||||||
|
// if result.Error != nil {
|
||||||
|
// if result.Error == gorm.ErrRecordNotFound && cfg.Enabled {
|
||||||
|
// // not here lets add it
|
||||||
|
|
||||||
|
// if err := db.Create(&cfg).Error; err != nil && !existing.DeletedAt.Valid {
|
||||||
|
// log.Error("Failed to seed settings", "settings", map[string]interface{}{
|
||||||
|
// "name": cfg.Name,
|
||||||
|
// "error": err,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //log.Printf("Seeded new config: %s", cfg.Name)
|
||||||
|
// } else {
|
||||||
|
// // Some other error
|
||||||
|
// return result.Error
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
// remove the setting if we change to false this will help with future proofing our seeder in the event we need to add it back
|
||||||
|
if cfg.Enabled {
|
||||||
|
existing.Description = cfg.Description
|
||||||
|
existing.Name = cfg.Name
|
||||||
|
existing.AppService = cfg.AppService
|
||||||
|
if err := db.Save(&existing).Error; err != nil {
|
||||||
|
log.Error("Failed to update ettings.", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we delete the setting so its no longer there
|
||||||
|
if err := db.Delete(&existing).Error; err != nil {
|
||||||
|
log.Error("Failed to delete ettings.", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settingDelete := fmt.Sprintf("Updated existing config: %s", cfg.Name)
|
||||||
|
log.Info(settingDelete, "settings", map[string]interface{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Printf("Updated existing config: %s", cfg.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("All settings added or updated.", "settings", map[string]interface{}{})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
110
backend/internal/system/settings/settings_states.go
Normal file
110
backend/internal/system/settings/settings_states.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Global state
|
||||||
|
appSettings []models.Settings
|
||||||
|
appSettingsLock sync.RWMutex
|
||||||
|
dbInstance *gorm.DB
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize loads settings into memory at startup
|
||||||
|
func Initialize(db *gorm.DB) error {
|
||||||
|
dbInstance = db
|
||||||
|
return Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh reloads settings from DB (call after updates)
|
||||||
|
func Refresh() error {
|
||||||
|
appSettingsLock.Lock()
|
||||||
|
defer appSettingsLock.Unlock()
|
||||||
|
|
||||||
|
var settings []models.Settings
|
||||||
|
if err := dbInstance.Find(&settings).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
appSettings = settings
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns a thread-safe copy of settings
|
||||||
|
func GetAll() []models.Settings {
|
||||||
|
appSettingsLock.RLock()
|
||||||
|
defer appSettingsLock.RUnlock()
|
||||||
|
|
||||||
|
// Return copy to prevent external modification
|
||||||
|
copied := make([]models.Settings, len(appSettings))
|
||||||
|
copy(copied, appSettings)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMap returns settings as []map[string]interface{}
|
||||||
|
func GetMap() []map[string]interface{} {
|
||||||
|
return convertToMap(GetAll())
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToMap helper (move your existing conversion logic here)
|
||||||
|
func convertToMap(settings []models.Settings) []map[string]interface{} {
|
||||||
|
toLowercase := func(s models.Settings) map[string]interface{} {
|
||||||
|
t := reflect.TypeOf(s)
|
||||||
|
v := reflect.ValueOf(s)
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := strings.ToLower(t.Field(i).Name)
|
||||||
|
data[field] = v.Field(i).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert each struct in settings slice to a map with lowercase keys
|
||||||
|
var lowercaseSettings []map[string]interface{}
|
||||||
|
for _, setting := range settings {
|
||||||
|
lowercaseSettings = append(lowercaseSettings, toLowercase(setting))
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowercaseSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetString(name string) (string, error) {
|
||||||
|
appSettingsLock.RLock()
|
||||||
|
defer appSettingsLock.RUnlock()
|
||||||
|
|
||||||
|
for _, s := range appSettings {
|
||||||
|
if s.Name == name { // assuming your model has a "Name" field
|
||||||
|
fmt.Println(s.Value)
|
||||||
|
return s.Value, nil // assuming your model has a "Value" field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("setting not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetTemp(name, value string) {
|
||||||
|
appSettingsLock.Lock()
|
||||||
|
defer appSettingsLock.Unlock()
|
||||||
|
|
||||||
|
for i, s := range appSettings {
|
||||||
|
if s.Name == name {
|
||||||
|
appSettings[i].Value = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If not found, add new setting
|
||||||
|
appSettings = append(appSettings, models.Settings{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
56
backend/internal/system/settings/update_setting.go
Normal file
56
backend/internal/system/settings/update_setting.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateSetting(log *logger.CustomLogger, db *gorm.DB, id string, input SettingUpdateInput) error {
|
||||||
|
var cfg models.Settings
|
||||||
|
if err := db.Where("setting_id =?", id).First(&cfg).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updates := map[string]interface{}{}
|
||||||
|
|
||||||
|
if input.Description != nil {
|
||||||
|
updates["description"] = *input.Description
|
||||||
|
}
|
||||||
|
if input.Value != nil {
|
||||||
|
updates["value"] = *input.Value
|
||||||
|
}
|
||||||
|
if input.Enabled != nil {
|
||||||
|
updates["enabled"] = *input.Enabled
|
||||||
|
}
|
||||||
|
if input.AppService != nil {
|
||||||
|
updates["app_service"] = *input.AppService
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updates) == 0 {
|
||||||
|
return nil // nothing to update
|
||||||
|
}
|
||||||
|
|
||||||
|
settingUpdate := db.Model(&cfg).Updates(updates)
|
||||||
|
|
||||||
|
if settingUpdate.Error != nil {
|
||||||
|
log.Error("There was an error updating the setting", "settings", map[string]interface{}{
|
||||||
|
"error": settingUpdate.Error,
|
||||||
|
})
|
||||||
|
return settingUpdate.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Refresh(); err != nil {
|
||||||
|
log.Error("There was an error refreshing the settings after a setting update", "settings", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("The setting was just updated", "settings", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"name": cfg.Name,
|
||||||
|
"updated": updates,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,20 +1,79 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"os"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/joho/godotenv"
|
||||||
|
|
||||||
|
"lst.net/internal/db"
|
||||||
|
"lst.net/internal/router"
|
||||||
|
"lst.net/internal/system/settings"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("Welcome to lst backend where all the fun happens")
|
log := logger.New()
|
||||||
r := gin.Default()
|
if os.Getenv("RUNNING_IN_DOCKER") != "true" {
|
||||||
|
err := godotenv.Load("../.env")
|
||||||
|
if err != nil {
|
||||||
|
log := logger.New()
|
||||||
|
log.Info("Warning: .env file not found (ok in Docker/production)", "system", map[string]interface{}{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r.GET("/", errorLoc)
|
// Initialize DB
|
||||||
r.Run(":8080")
|
if _, err := db.InitDB(); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func errorLoc(c *gin.Context) {
|
log.Panic("Database intialize failed, please check the server asap.", "db", map[string]interface{}{
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "welcome to lst system you might have just encountered an incorrect area of the app"})
|
"error": err.Error(),
|
||||||
|
"cause": errors.Unwrap(err),
|
||||||
|
"timeout": "30s",
|
||||||
|
"details": fmt.Sprintf("%+v", err), // Full stack trace if available
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
|
||||||
|
sqlDB, _ := db.DB.DB()
|
||||||
|
sqlDB.Close()
|
||||||
|
log.Error("Recovered from panic during DB shutdown", "db", map[string]interface{}{
|
||||||
|
"panic": r,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := settings.Initialize(db.DB); err != nil {
|
||||||
|
log.Panic("There was an error intilizing the settings", "settings", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// long lived process like ocp running all the time should go here and base the db struct over.
|
||||||
|
// go ocp.MonitorPrinters
|
||||||
|
// go notifcations.Processor
|
||||||
|
|
||||||
|
// Set basePath dynamically
|
||||||
|
basePath := "/"
|
||||||
|
|
||||||
|
if os.Getenv("APP_ENV") != "production" {
|
||||||
|
basePath = "/lst" // Dev only
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Welcome to lst backend where all the fun happens.", "system", map[string]interface{}{})
|
||||||
|
// Init Gin router and pass DB to services
|
||||||
|
r := router.Setup(db.DB, basePath, log)
|
||||||
|
|
||||||
|
// get the server port
|
||||||
|
port := "8080"
|
||||||
|
if os.Getenv("VITE_SERVER_PORT") != "" {
|
||||||
|
port = os.Getenv("VITE_SERVER_PORT")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Run(":" + port); err != nil {
|
||||||
|
log.Panic("Server failed to start", "system", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
backend/pkg/json.go
Normal file
3
backend/pkg/json.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
type JSONB map[string]interface{}
|
||||||
18
backend/pkg/logger/create_log.go
Normal file
18
backend/pkg/logger/create_log.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lst.net/internal/db"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateLog inserts a new log entry.
|
||||||
|
func CreateLog(level, message, service string, metadata pkg.JSONB) error {
|
||||||
|
log := models.Log{
|
||||||
|
Level: level,
|
||||||
|
Message: message,
|
||||||
|
Service: service,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
return db.DB.Create(&log).Error
|
||||||
|
}
|
||||||
77
backend/pkg/logger/discord.go
Normal file
77
backend/pkg/logger/discord.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
discordwebhook "github.com/bensch777/discord-webhook-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateDiscordMsg(message string) {
|
||||||
|
// we will only run the discord bot if we actaully put a url in the.
|
||||||
|
if os.Getenv("WEBHOOK") != "" {
|
||||||
|
var webhookurl = os.Getenv("WEBHOOK")
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
embed := discordwebhook.Embed{
|
||||||
|
Title: "A new crash report from lst.",
|
||||||
|
Color: 15277667,
|
||||||
|
Url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
// Thumbnail: discordwebhook.Thumbnail{
|
||||||
|
// Url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
// },
|
||||||
|
// Author: discordwebhook.Author{
|
||||||
|
// Name: "Author Name",
|
||||||
|
// Icon_URL: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
// },
|
||||||
|
Fields: []discordwebhook.Field{
|
||||||
|
discordwebhook.Field{
|
||||||
|
Name: host,
|
||||||
|
Value: message,
|
||||||
|
Inline: false,
|
||||||
|
},
|
||||||
|
// discordwebhook.Field{
|
||||||
|
// Name: "Error reason",
|
||||||
|
// Value: stack,
|
||||||
|
// Inline: false,
|
||||||
|
// },
|
||||||
|
// discordwebhook.Field{
|
||||||
|
// Name: "Field 3",
|
||||||
|
// Value: "Field Value 3",
|
||||||
|
// Inline: false,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
// Footer: discordwebhook.Footer{
|
||||||
|
// Text: "Footer Text",
|
||||||
|
// Icon_url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
|
||||||
|
SendEmbed(webhookurl, embed)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendEmbed(link string, embeds discordwebhook.Embed) error {
|
||||||
|
logging := New()
|
||||||
|
logging.Info("new messege being posted to discord", "logger", map[string]interface{}{
|
||||||
|
"message": "Message",
|
||||||
|
})
|
||||||
|
hook := discordwebhook.Hook{
|
||||||
|
Username: "Captain Hook",
|
||||||
|
Avatar_url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
Content: "Message",
|
||||||
|
Embeds: []discordwebhook.Embed{embeds},
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(hook)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = discordwebhook.ExecuteWebhook(link, payload)
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
117
backend/pkg/logger/logger.go
Normal file
117
backend/pkg/logger/logger.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomLogger struct {
|
||||||
|
consoleLogger zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
Data map[string]interface{} `json:"data"`
|
||||||
|
Meta map[string]interface{} `json:"meta,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a configured CustomLogger.
|
||||||
|
func New() *CustomLogger {
|
||||||
|
// Colorized console output
|
||||||
|
consoleWriter := zerolog.ConsoleWriter{
|
||||||
|
Out: os.Stderr,
|
||||||
|
TimeFormat: "2006-01-02 15:04:05",
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CustomLogger{
|
||||||
|
consoleLogger: zerolog.New(consoleWriter).
|
||||||
|
With().
|
||||||
|
Timestamp().
|
||||||
|
Logger(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrettyFormat(level, message string, metadata map[string]interface{}) string {
|
||||||
|
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
base := fmt.Sprintf("[%s] %s| Message: %s", strings.ToUpper(level), timestamp, message)
|
||||||
|
|
||||||
|
if len(metadata) > 0 {
|
||||||
|
metaJSON, _ := json.Marshal(metadata)
|
||||||
|
return fmt.Sprintf("%s | Metadata: %s", base, string(metaJSON))
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) logToPostgres(level, message, service string, metadata map[string]interface{}) {
|
||||||
|
err := CreateLog(level, message, service, metadata)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback to console if DB fails
|
||||||
|
log.Error().Err(err).Msg("Failed to write log to PostgreSQL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Level-Specific Methods ---
|
||||||
|
|
||||||
|
func (l *CustomLogger) Info(message, service string, fields map[string]interface{}) {
|
||||||
|
l.consoleLogger.Info().Fields(fields).Msg(message)
|
||||||
|
l.logToPostgres("info", message, service, fields)
|
||||||
|
|
||||||
|
//PostLog(PrettyFormat("info", message, fields)) // Broadcast pretty message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Warn(message, service string, fields map[string]interface{}) {
|
||||||
|
l.consoleLogger.Error().Fields(fields).Msg(message)
|
||||||
|
l.logToPostgres("warn", message, service, fields)
|
||||||
|
|
||||||
|
//PostLog(PrettyFormat("warn", message, fields)) // Broadcast pretty message
|
||||||
|
|
||||||
|
// Custom logic for errors (e.g., alerting)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
l.consoleLogger.Warn().Msg("Additional error context captured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Error(message, service string, fields map[string]interface{}) {
|
||||||
|
l.consoleLogger.Error().Fields(fields).Msg(message)
|
||||||
|
l.logToPostgres("error", message, service, fields)
|
||||||
|
|
||||||
|
//PostLog(PrettyFormat("error", message, fields)) // Broadcast pretty message
|
||||||
|
|
||||||
|
// Custom logic for errors (e.g., alerting)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
l.consoleLogger.Warn().Msg("Additional error context captured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Panic(message, service string, fields map[string]interface{}) {
|
||||||
|
// Log to console (colored, with fields)
|
||||||
|
l.consoleLogger.Error().
|
||||||
|
Str("service", service).
|
||||||
|
Fields(fields).
|
||||||
|
Msg(message + " (PANIC)") // Explicitly mark as panic
|
||||||
|
|
||||||
|
// Log to PostgreSQL (sync to ensure it's saved before crashing)
|
||||||
|
err := CreateLog("panic", message, service, fields) // isCritical=true
|
||||||
|
if err != nil {
|
||||||
|
l.consoleLogger.Error().Err(err).Msg("Failed to save panic log to PostgreSQL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional context (optional)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
l.consoleLogger.Warn().Msg("Additional panic context captured")
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDiscordMsg(message)
|
||||||
|
panic(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Debug(message, service string, fields map[string]interface{}) {
|
||||||
|
l.consoleLogger.Debug().Fields(fields).Msg(message)
|
||||||
|
l.logToPostgres("debug", message, service, fields)
|
||||||
|
}
|
||||||
@@ -1,21 +1,26 @@
|
|||||||
---
|
---
|
||||||
services:
|
services:
|
||||||
lst_backend:
|
lst_backend:
|
||||||
# build: . # Tell Docker Compose to build the image using the Dockerfile in the current directory
|
build:
|
||||||
image: git.tuffraid.net/cowch/logistics_support_tool:backend-latest
|
context: .
|
||||||
container_name: lst_backend # A friendly name for your running container
|
dockerfile: ./backend/Dockerfile
|
||||||
volumes:
|
no_cache: true
|
||||||
- /path/to/frontend/backend:/data
|
image: git.tuffraid.net/cowch/logistics_support_tool:latest
|
||||||
ports:
|
container_name: lst_backend
|
||||||
- "8080:8080"
|
networks:
|
||||||
restart: unless-stopped
|
- docker-network
|
||||||
|
environment:
|
||||||
lst_frontend:
|
DB_HOST: postgres
|
||||||
# build: . # Tell Docker Compose to build the image using the Dockerfile in the current directory
|
DB_PORT: 5432
|
||||||
image: git.tuffraid.net/cowch/logistics_support_tool:frontend-latest
|
DB_USER: username
|
||||||
container_name: lst_frontend # A friendly name for your running container
|
DB_PASSWORD: passwordl
|
||||||
volumes:
|
DB_NAME: lst
|
||||||
- /path/to/frontend/data:/data
|
volumes:
|
||||||
ports:
|
- /path/to/backend/data:/data
|
||||||
- "3120:3000"
|
ports:
|
||||||
restart: unless-stopped
|
- "8080:8080"
|
||||||
|
restart: unless-stopped
|
||||||
|
pull_policy: never
|
||||||
|
networks:
|
||||||
|
docker-network:
|
||||||
|
external: true
|
||||||
|
|||||||
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Build Stage
|
|
||||||
FROM node:24-alpine AS deps
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Build the Next.js app
|
|
||||||
FROM node:24-alpine AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . ./
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
|
||||||
# Run other commands like prisma or drizzle
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# if more commands are needed after here do the same
|
|
||||||
|
|
||||||
# Final stage
|
|
||||||
FROM node:24-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=builder /app/.nitro /app/.nitro
|
|
||||||
COPY --from=builder /app/.output /app/.output
|
|
||||||
COPY --from=builder /app/.tanstack /app/.tanstack
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD ["node", ".output/server/index.mjs"]
|
|
||||||
69
frontend/README.md
Normal file
69
frontend/README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default tseslint.config([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
|
||||||
|
// Remove tseslint.configs.recommended and replace with this
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
// Alternatively, use this for stricter rules
|
||||||
|
...tseslint.configs.strictTypeChecked,
|
||||||
|
// Optionally, add this for stylistic rules
|
||||||
|
...tseslint.configs.stylisticTypeChecked,
|
||||||
|
|
||||||
|
// Other configs...
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// eslint.config.js
|
||||||
|
import reactX from 'eslint-plugin-react-x'
|
||||||
|
import reactDom from 'eslint-plugin-react-dom'
|
||||||
|
|
||||||
|
export default tseslint.config([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
// Enable lint rules for React
|
||||||
|
reactX.configs['recommended-typescript'],
|
||||||
|
// Enable lint rules for React DOM
|
||||||
|
reactDom.configs.recommended,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
23
frontend/eslint.config.js
Normal file
23
frontend/eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default tseslint.config([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs['recommended-latest'],
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9620
frontend/package-lock.json
generated
9620
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "1.0.0",
|
"private": true,
|
||||||
"description": "",
|
"version": "0.0.0",
|
||||||
"main": "index.js",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite",
|
||||||
"build": "vite build"
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-router": "^1.127.3",
|
|
||||||
"@tanstack/react-start": "^1.127.4",
|
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0"
|
||||||
"vite": "^7.0.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.30.1",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"typescript": "^5.8.3",
|
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"eslint": "^9.30.1",
|
||||||
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
|
"globals": "^16.3.0",
|
||||||
|
"typescript": "~5.8.3",
|
||||||
|
"typescript-eslint": "^8.35.1",
|
||||||
|
"vite": "^7.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
frontend/public/vite.svg
Normal file
1
frontend/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
42
frontend/src/App.css
Normal file
42
frontend/src/App.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#root {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 6em;
|
||||||
|
padding: 1.5em;
|
||||||
|
will-change: filter;
|
||||||
|
transition: filter 300ms;
|
||||||
|
}
|
||||||
|
.logo:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #646cffaa);
|
||||||
|
}
|
||||||
|
.logo.react:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
a:nth-of-type(2) .logo {
|
||||||
|
animation: logo-spin infinite 20s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-the-docs {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
41
frontend/src/App.tsx
Normal file
41
frontend/src/App.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import reactLogo from "./assets/react.svg";
|
||||||
|
import viteLogo from "/vite.svg";
|
||||||
|
import "./App.css";
|
||||||
|
import WebSocketViewer from "./WebSocketTest";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<a href="https://vite.dev" target="_blank">
|
||||||
|
<img src={viteLogo} className="logo" alt="Vite logo" />
|
||||||
|
</a>
|
||||||
|
<a href="https://react.dev" target="_blank">
|
||||||
|
<img
|
||||||
|
src={reactLogo}
|
||||||
|
className="logo react"
|
||||||
|
alt="React logo"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1>Vite + React</h1>
|
||||||
|
<div className="card">
|
||||||
|
<button onClick={() => setCount((count) => count + 1)}>
|
||||||
|
count is {count}
|
||||||
|
</button>
|
||||||
|
<p>
|
||||||
|
Edit <code>src/App.tsx</code> and save to test HMR
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="read-the-docs">
|
||||||
|
Click on the Vite and React logos to learn more
|
||||||
|
</p>
|
||||||
|
<WebSocketViewer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
41
frontend/src/WebSocketTest.tsx
Normal file
41
frontend/src/WebSocketTest.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
const WebSocketViewer = () => {
|
||||||
|
const ws = useRef<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Connect to your Go backend WebSocket endpoint
|
||||||
|
ws.current = new WebSocket(
|
||||||
|
(window.location.protocol === "https:" ? "wss://" : "ws://") +
|
||||||
|
window.location.host +
|
||||||
|
"/lst/ws"
|
||||||
|
);
|
||||||
|
|
||||||
|
ws.current.onopen = () => {
|
||||||
|
console.log("[WebSocket] Connected");
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.current.onmessage = (event: any) => {
|
||||||
|
console.log("[WebSocket] Message received:", event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.current.onerror = (error: any) => {
|
||||||
|
console.error("[WebSocket] Error:", error);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.current.onclose = () => {
|
||||||
|
console.log("[WebSocket] Disconnected");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cleanup on unmount
|
||||||
|
return () => {
|
||||||
|
if (ws.current) {
|
||||||
|
ws.current.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div>Check the console for WebSocket messages</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WebSocketViewer;
|
||||||
1
frontend/src/assets/react.svg
Normal file
1
frontend/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
68
frontend/src/index.css
Normal file
68
frontend/src/index.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
:root {
|
||||||
|
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
frontend/src/main.tsx
Normal file
10
frontend/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
|
|
||||||
// This file was automatically generated by TanStack Router.
|
|
||||||
// You should NOT make any changes in this file as it will be overwritten.
|
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
|
||||||
|
|
||||||
const IndexRoute = IndexRouteImport.update({
|
|
||||||
id: '/',
|
|
||||||
path: '/',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesByTo {
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesById {
|
|
||||||
__root__: typeof rootRouteImport
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
}
|
|
||||||
export interface FileRouteTypes {
|
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
|
||||||
fullPaths: '/'
|
|
||||||
fileRoutesByTo: FileRoutesByTo
|
|
||||||
to: '/'
|
|
||||||
id: '__root__' | '/'
|
|
||||||
fileRoutesById: FileRoutesById
|
|
||||||
}
|
|
||||||
export interface RootRouteChildren {
|
|
||||||
IndexRoute: typeof IndexRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
|
||||||
interface FileRoutesByPath {
|
|
||||||
'/': {
|
|
||||||
id: '/'
|
|
||||||
path: '/'
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof IndexRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
|
||||||
IndexRoute: IndexRoute,
|
|
||||||
}
|
|
||||||
export const routeTree = rootRouteImport
|
|
||||||
._addFileChildren(rootRouteChildren)
|
|
||||||
._addFileTypes<FileRouteTypes>()
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// src/router.tsx
|
|
||||||
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
|
|
||||||
import { routeTree } from './routeTree.gen'
|
|
||||||
|
|
||||||
export function createRouter() {
|
|
||||||
const router = createTanStackRouter({
|
|
||||||
basepath: '/lst',
|
|
||||||
routeTree,
|
|
||||||
scrollRestoration: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
|
||||||
interface Register {
|
|
||||||
router: ReturnType<typeof createRouter>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
import type { ReactNode } from 'react'
|
|
||||||
import {
|
|
||||||
Outlet,
|
|
||||||
createRootRoute,
|
|
||||||
HeadContent,
|
|
||||||
Scripts,
|
|
||||||
} from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
|
||||||
head: () => ({
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
charSet: 'utf-8',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'viewport',
|
|
||||||
content: 'width=device-width, initial-scale=1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'TanStack Start Starter',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
component: RootComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
function RootComponent() {
|
|
||||||
return (
|
|
||||||
<RootDocument>
|
|
||||||
<Outlet />
|
|
||||||
</RootDocument>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
|
|
||||||
return (
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<HeadContent />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{children}
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// src/routes/index.tsx
|
|
||||||
import * as fs from 'node:fs'
|
|
||||||
import { createFileRoute, useRouter } from '@tanstack/react-router'
|
|
||||||
import { createServerFn } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
const filePath = 'count.txt'
|
|
||||||
|
|
||||||
async function readCount() {
|
|
||||||
return parseInt(
|
|
||||||
await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCount = createServerFn({
|
|
||||||
method: 'GET',
|
|
||||||
}).handler(() => {
|
|
||||||
return readCount()
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateCount = createServerFn({ method: 'POST' })
|
|
||||||
.validator((d: number) => d)
|
|
||||||
.handler(async ({ data }) => {
|
|
||||||
const count = await readCount()
|
|
||||||
await fs.promises.writeFile(filePath, `${count + data}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({
|
|
||||||
component: Home,
|
|
||||||
loader: async () => await getCount(),
|
|
||||||
})
|
|
||||||
|
|
||||||
function Home() {
|
|
||||||
const router = useRouter()
|
|
||||||
const state = Route.useLoaderData()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
updateCount({ data: 1 }).then(() => {
|
|
||||||
router.invalidate()
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add 1 to {state}?
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
1
frontend/src/vite-env.d.ts
vendored
Normal file
1
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
27
frontend/tsconfig.app.json
Normal file
27
frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"files": [],
|
||||||
"jsx": "react-jsx",
|
"references": [
|
||||||
"moduleResolution": "Bundler",
|
{ "path": "./tsconfig.app.json" },
|
||||||
"module": "ESNext",
|
{ "path": "./tsconfig.node.json" }
|
||||||
"target": "ES2022",
|
]
|
||||||
"skipLibCheck": true,
|
|
||||||
"strictNullChecks": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
25
frontend/tsconfig.node.json
Normal file
25
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -1,11 +1,40 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import tsConfigPaths from 'vite-tsconfig-paths'
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
||||||
|
|
||||||
|
import path from "path";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
dotenv.config({
|
||||||
|
path: path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.env"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: '/lst/',
|
plugins: [react()],
|
||||||
server: {
|
base: "/lst/app/",
|
||||||
port: 3000,
|
build: {
|
||||||
},
|
outDir: "../backend/frontend",
|
||||||
plugins: [tsConfigPaths(), tanstackStart({ target: 'node-server' })],
|
assetsDir: "assets",
|
||||||
})
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
"/lst/api": {
|
||||||
|
target: `http://localhost:${Number(
|
||||||
|
process.env.VITE_SERVER_PORT || 8080
|
||||||
|
)}`,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
"/lst/ws": {
|
||||||
|
target: `ws://localhost:${Number(
|
||||||
|
process.env.VITE_SERVER_PORT || 8080
|
||||||
|
)}`, // Your Go WebSocket endpoint
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
rewrite: (path) => path.replace(/^\/ws/, ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
20
lst-docs/.gitignore
vendored
Normal file
20
lst-docs/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.docusaurus
|
||||||
|
.cache-loader
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
41
lst-docs/README.md
Normal file
41
lst-docs/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Website
|
||||||
|
|
||||||
|
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Using SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
USE_SSH=true yarn deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Not using SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GIT_USER=<Your GitHub username> yarn deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||||
12
lst-docs/blog/2019-05-28-first-blog-post.md
Normal file
12
lst-docs/blog/2019-05-28-first-blog-post.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
slug: first-blog-post
|
||||||
|
title: First Blog Post
|
||||||
|
authors: [slorber, yangshun]
|
||||||
|
tags: [hola, docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet...
|
||||||
|
|
||||||
|
<!-- truncate -->
|
||||||
|
|
||||||
|
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
44
lst-docs/blog/2019-05-29-long-blog-post.md
Normal file
44
lst-docs/blog/2019-05-29-long-blog-post.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
slug: long-blog-post
|
||||||
|
title: Long Blog Post
|
||||||
|
authors: yangshun
|
||||||
|
tags: [hello, docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
This is the summary of a very long blog post,
|
||||||
|
|
||||||
|
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
|
||||||
|
|
||||||
|
<!-- truncate -->
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
24
lst-docs/blog/2021-08-01-mdx-blog-post.mdx
Normal file
24
lst-docs/blog/2021-08-01-mdx-blog-post.mdx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
slug: mdx-blog-post
|
||||||
|
title: MDX Blog Post
|
||||||
|
authors: [slorber]
|
||||||
|
tags: [docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
Use the power of React to create interactive blog posts.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
{/* truncate */}
|
||||||
|
|
||||||
|
For example, use JSX to create an interactive button:
|
||||||
|
|
||||||
|
```js
|
||||||
|
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
||||||
BIN
lst-docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg
Normal file
BIN
lst-docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
29
lst-docs/blog/2021-08-26-welcome/index.md
Normal file
29
lst-docs/blog/2021-08-26-welcome/index.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
slug: welcome
|
||||||
|
title: Welcome
|
||||||
|
authors: [slorber, yangshun]
|
||||||
|
tags: [facebook, hello, docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
|
||||||
|
|
||||||
|
Here are a few tips you might find useful.
|
||||||
|
|
||||||
|
<!-- truncate -->
|
||||||
|
|
||||||
|
Simply add Markdown files (or folders) to the `blog` directory.
|
||||||
|
|
||||||
|
Regular blog authors can be added to `authors.yml`.
|
||||||
|
|
||||||
|
The blog post date can be extracted from filenames, such as:
|
||||||
|
|
||||||
|
- `2019-05-30-welcome.md`
|
||||||
|
- `2019-05-30-welcome/index.md`
|
||||||
|
|
||||||
|
A blog post folder can be convenient to co-locate blog post images:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The blog supports tags as well!
|
||||||
|
|
||||||
|
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
|
||||||
25
lst-docs/blog/authors.yml
Normal file
25
lst-docs/blog/authors.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
yangshun:
|
||||||
|
name: Yangshun Tay
|
||||||
|
title: Ex-Meta Staff Engineer, Co-founder GreatFrontEnd
|
||||||
|
url: https://linkedin.com/in/yangshun
|
||||||
|
image_url: https://github.com/yangshun.png
|
||||||
|
page: true
|
||||||
|
socials:
|
||||||
|
x: yangshunz
|
||||||
|
linkedin: yangshun
|
||||||
|
github: yangshun
|
||||||
|
newsletter: https://www.greatfrontend.com
|
||||||
|
|
||||||
|
slorber:
|
||||||
|
name: Sébastien Lorber
|
||||||
|
title: Docusaurus maintainer
|
||||||
|
url: https://sebastienlorber.com
|
||||||
|
image_url: https://github.com/slorber.png
|
||||||
|
page:
|
||||||
|
# customize the url of the author page at /blog/authors/<permalink>
|
||||||
|
permalink: '/all-sebastien-lorber-articles'
|
||||||
|
socials:
|
||||||
|
x: sebastienlorber
|
||||||
|
linkedin: sebastienlorber
|
||||||
|
github: slorber
|
||||||
|
newsletter: https://thisweekinreact.com
|
||||||
19
lst-docs/blog/tags.yml
Normal file
19
lst-docs/blog/tags.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
facebook:
|
||||||
|
label: Facebook
|
||||||
|
permalink: /facebook
|
||||||
|
description: Facebook tag description
|
||||||
|
|
||||||
|
hello:
|
||||||
|
label: Hello
|
||||||
|
permalink: /hello
|
||||||
|
description: Hello tag description
|
||||||
|
|
||||||
|
docusaurus:
|
||||||
|
label: Docusaurus
|
||||||
|
permalink: /docusaurus
|
||||||
|
description: Docusaurus tag description
|
||||||
|
|
||||||
|
hola:
|
||||||
|
label: Hola
|
||||||
|
permalink: /hola
|
||||||
|
description: Hola tag description
|
||||||
2
lst-docs/copy.ps1
Normal file
2
lst-docs/copy.ps1
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Write-Host "Copy files to the backend"
|
||||||
|
robocopy build ..\backend\docs /E /NFL /NDL
|
||||||
47
lst-docs/docs/intro.md
Normal file
47
lst-docs/docs/intro.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tutorial Intro
|
||||||
|
|
||||||
|
Let's discover **Docusaurus in less than 5 minutes**.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Get started by **creating a new site**.
|
||||||
|
|
||||||
|
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
|
||||||
|
|
||||||
|
### What you'll need
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/en/download/) version 18.0 or above:
|
||||||
|
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||||
|
|
||||||
|
## Generate a new site
|
||||||
|
|
||||||
|
Generate a new Docusaurus site using the **classic template**.
|
||||||
|
|
||||||
|
The classic template will automatically be added to your project after you run the command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm init docusaurus@latest my-website classic
|
||||||
|
```
|
||||||
|
|
||||||
|
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
|
||||||
|
|
||||||
|
The command also installs all necessary dependencies you need to run Docusaurus.
|
||||||
|
|
||||||
|
## Start your site
|
||||||
|
|
||||||
|
Run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd my-website
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
|
||||||
|
|
||||||
|
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
|
||||||
|
|
||||||
|
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
|
||||||
8
lst-docs/docs/tutorial-basics/_category_.json
Normal file
8
lst-docs/docs/tutorial-basics/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"label": "Tutorial - Basics",
|
||||||
|
"position": 2,
|
||||||
|
"link": {
|
||||||
|
"type": "generated-index",
|
||||||
|
"description": "5 minutes to learn the most important Docusaurus concepts."
|
||||||
|
}
|
||||||
|
}
|
||||||
23
lst-docs/docs/tutorial-basics/congratulations.md
Normal file
23
lst-docs/docs/tutorial-basics/congratulations.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
# Congratulations!
|
||||||
|
|
||||||
|
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
|
||||||
|
|
||||||
|
Docusaurus has **much more to offer**!
|
||||||
|
|
||||||
|
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
|
||||||
|
|
||||||
|
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
|
||||||
|
|
||||||
|
## What's next?
|
||||||
|
|
||||||
|
- Read the [official documentation](https://docusaurus.io/)
|
||||||
|
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
|
||||||
|
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
|
||||||
|
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
|
||||||
|
- Add a [search bar](https://docusaurus.io/docs/search)
|
||||||
|
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
|
||||||
|
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)
|
||||||
34
lst-docs/docs/tutorial-basics/create-a-blog-post.md
Normal file
34
lst-docs/docs/tutorial-basics/create-a-blog-post.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create a Blog Post
|
||||||
|
|
||||||
|
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
|
||||||
|
|
||||||
|
## Create your first Post
|
||||||
|
|
||||||
|
Create a file at `blog/2021-02-28-greetings.md`:
|
||||||
|
|
||||||
|
```md title="blog/2021-02-28-greetings.md"
|
||||||
|
---
|
||||||
|
slug: greetings
|
||||||
|
title: Greetings!
|
||||||
|
authors:
|
||||||
|
- name: Joel Marcey
|
||||||
|
title: Co-creator of Docusaurus 1
|
||||||
|
url: https://github.com/JoelMarcey
|
||||||
|
image_url: https://github.com/JoelMarcey.png
|
||||||
|
- name: Sébastien Lorber
|
||||||
|
title: Docusaurus maintainer
|
||||||
|
url: https://sebastienlorber.com
|
||||||
|
image_url: https://github.com/slorber.png
|
||||||
|
tags: [greetings]
|
||||||
|
---
|
||||||
|
|
||||||
|
Congratulations, you have made your first post!
|
||||||
|
|
||||||
|
Feel free to play around and edit this post as much as you like.
|
||||||
|
```
|
||||||
|
|
||||||
|
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).
|
||||||
57
lst-docs/docs/tutorial-basics/create-a-document.md
Normal file
57
lst-docs/docs/tutorial-basics/create-a-document.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create a Document
|
||||||
|
|
||||||
|
Documents are **groups of pages** connected through:
|
||||||
|
|
||||||
|
- a **sidebar**
|
||||||
|
- **previous/next navigation**
|
||||||
|
- **versioning**
|
||||||
|
|
||||||
|
## Create your first Doc
|
||||||
|
|
||||||
|
Create a Markdown file at `docs/hello.md`:
|
||||||
|
|
||||||
|
```md title="docs/hello.md"
|
||||||
|
# Hello
|
||||||
|
|
||||||
|
This is my **first Docusaurus document**!
|
||||||
|
```
|
||||||
|
|
||||||
|
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
|
||||||
|
|
||||||
|
## Configure the Sidebar
|
||||||
|
|
||||||
|
Docusaurus automatically **creates a sidebar** from the `docs` folder.
|
||||||
|
|
||||||
|
Add metadata to customize the sidebar label and position:
|
||||||
|
|
||||||
|
```md title="docs/hello.md" {1-4}
|
||||||
|
---
|
||||||
|
sidebar_label: 'Hi!'
|
||||||
|
sidebar_position: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hello
|
||||||
|
|
||||||
|
This is my **first Docusaurus document**!
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to create your sidebar explicitly in `sidebars.js`:
|
||||||
|
|
||||||
|
```js title="sidebars.js"
|
||||||
|
export default {
|
||||||
|
tutorialSidebar: [
|
||||||
|
'intro',
|
||||||
|
// highlight-next-line
|
||||||
|
'hello',
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Tutorial',
|
||||||
|
items: ['tutorial-basics/create-a-document'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
43
lst-docs/docs/tutorial-basics/create-a-page.md
Normal file
43
lst-docs/docs/tutorial-basics/create-a-page.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create a Page
|
||||||
|
|
||||||
|
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
|
||||||
|
|
||||||
|
- `src/pages/index.js` → `localhost:3000/`
|
||||||
|
- `src/pages/foo.md` → `localhost:3000/foo`
|
||||||
|
- `src/pages/foo/bar.js` → `localhost:3000/foo/bar`
|
||||||
|
|
||||||
|
## Create your first React Page
|
||||||
|
|
||||||
|
Create a file at `src/pages/my-react-page.js`:
|
||||||
|
|
||||||
|
```jsx title="src/pages/my-react-page.js"
|
||||||
|
import React from 'react';
|
||||||
|
import Layout from '@theme/Layout';
|
||||||
|
|
||||||
|
export default function MyReactPage() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<h1>My React page</h1>
|
||||||
|
<p>This is a React page</p>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
|
||||||
|
|
||||||
|
## Create your first Markdown Page
|
||||||
|
|
||||||
|
Create a file at `src/pages/my-markdown-page.md`:
|
||||||
|
|
||||||
|
```mdx title="src/pages/my-markdown-page.md"
|
||||||
|
# My Markdown page
|
||||||
|
|
||||||
|
This is a Markdown page
|
||||||
|
```
|
||||||
|
|
||||||
|
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).
|
||||||
31
lst-docs/docs/tutorial-basics/deploy-your-site.md
Normal file
31
lst-docs/docs/tutorial-basics/deploy-your-site.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Deploy your site
|
||||||
|
|
||||||
|
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
|
||||||
|
|
||||||
|
It builds your site as simple **static HTML, JavaScript and CSS files**.
|
||||||
|
|
||||||
|
## Build your site
|
||||||
|
|
||||||
|
Build your site **for production**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The static files are generated in the `build` folder.
|
||||||
|
|
||||||
|
## Deploy your site
|
||||||
|
|
||||||
|
Test your production build locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
|
||||||
|
|
||||||
|
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).
|
||||||
152
lst-docs/docs/tutorial-basics/markdown-features.mdx
Normal file
152
lst-docs/docs/tutorial-basics/markdown-features.mdx
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 4
|
||||||
|
---
|
||||||
|
|
||||||
|
# Markdown Features
|
||||||
|
|
||||||
|
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
|
||||||
|
|
||||||
|
## Front Matter
|
||||||
|
|
||||||
|
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
|
||||||
|
|
||||||
|
```text title="my-doc.md"
|
||||||
|
// highlight-start
|
||||||
|
---
|
||||||
|
id: my-doc-id
|
||||||
|
title: My document title
|
||||||
|
description: My document description
|
||||||
|
slug: /my-custom-url
|
||||||
|
---
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
|
## Markdown heading
|
||||||
|
|
||||||
|
Markdown text with [links](./hello.md)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
Regular Markdown links are supported, using url paths or relative file paths.
|
||||||
|
|
||||||
|
```md
|
||||||
|
Let's see how to [Create a page](/create-a-page).
|
||||||
|
```
|
||||||
|
|
||||||
|
```md
|
||||||
|
Let's see how to [Create a page](./create-a-page.md).
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:** Let's see how to [Create a page](./create-a-page.md).
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
Regular Markdown images are supported.
|
||||||
|
|
||||||
|
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Blocks
|
||||||
|
|
||||||
|
Markdown code blocks are supported with Syntax highlighting.
|
||||||
|
|
||||||
|
````md
|
||||||
|
```jsx title="src/components/HelloDocusaurus.js"
|
||||||
|
function HelloDocusaurus() {
|
||||||
|
return <h1>Hello, Docusaurus!</h1>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```jsx title="src/components/HelloDocusaurus.js"
|
||||||
|
function HelloDocusaurus() {
|
||||||
|
return <h1>Hello, Docusaurus!</h1>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Admonitions
|
||||||
|
|
||||||
|
Docusaurus has a special syntax to create admonitions and callouts:
|
||||||
|
|
||||||
|
```md
|
||||||
|
:::tip My tip
|
||||||
|
|
||||||
|
Use this awesome feature option
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::danger Take care
|
||||||
|
|
||||||
|
This action is dangerous
|
||||||
|
|
||||||
|
:::
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip My tip
|
||||||
|
|
||||||
|
Use this awesome feature option
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::danger Take care
|
||||||
|
|
||||||
|
This action is dangerous
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## MDX and React Components
|
||||||
|
|
||||||
|
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
export const Highlight = ({children, color}) => (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
backgroundColor: color,
|
||||||
|
borderRadius: '20px',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '10px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
alert(`You clicked the color ${color} with label ${children}`)
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||||
|
|
||||||
|
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||||
|
```
|
||||||
|
|
||||||
|
export const Highlight = ({children, color}) => (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
backgroundColor: color,
|
||||||
|
borderRadius: '20px',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '10px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
alert(`You clicked the color ${color} with label ${children}`);
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||||
|
|
||||||
|
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||||
7
lst-docs/docs/tutorial-extras/_category_.json
Normal file
7
lst-docs/docs/tutorial-extras/_category_.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"label": "Tutorial - Extras",
|
||||||
|
"position": 3,
|
||||||
|
"link": {
|
||||||
|
"type": "generated-index"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
lst-docs/docs/tutorial-extras/img/docsVersionDropdown.png
Normal file
BIN
lst-docs/docs/tutorial-extras/img/docsVersionDropdown.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
lst-docs/docs/tutorial-extras/img/localeDropdown.png
Normal file
BIN
lst-docs/docs/tutorial-extras/img/localeDropdown.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
55
lst-docs/docs/tutorial-extras/manage-docs-versions.md
Normal file
55
lst-docs/docs/tutorial-extras/manage-docs-versions.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manage Docs Versions
|
||||||
|
|
||||||
|
Docusaurus can manage multiple versions of your docs.
|
||||||
|
|
||||||
|
## Create a docs version
|
||||||
|
|
||||||
|
Release a version 1.0 of your project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run docusaurus docs:version 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
|
||||||
|
|
||||||
|
Your docs now have 2 versions:
|
||||||
|
|
||||||
|
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
|
||||||
|
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
|
||||||
|
|
||||||
|
## Add a Version Dropdown
|
||||||
|
|
||||||
|
To navigate seamlessly across versions, add a version dropdown.
|
||||||
|
|
||||||
|
Modify the `docusaurus.config.js` file:
|
||||||
|
|
||||||
|
```js title="docusaurus.config.js"
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
navbar: {
|
||||||
|
items: [
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
type: 'docsVersionDropdown',
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The docs version dropdown appears in your navbar:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Update an existing version
|
||||||
|
|
||||||
|
It is possible to edit versioned docs in their respective folder:
|
||||||
|
|
||||||
|
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
|
||||||
|
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`
|
||||||
88
lst-docs/docs/tutorial-extras/translate-your-site.md
Normal file
88
lst-docs/docs/tutorial-extras/translate-your-site.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Translate your site
|
||||||
|
|
||||||
|
Let's translate `docs/intro.md` to French.
|
||||||
|
|
||||||
|
## Configure i18n
|
||||||
|
|
||||||
|
Modify `docusaurus.config.js` to add support for the `fr` locale:
|
||||||
|
|
||||||
|
```js title="docusaurus.config.js"
|
||||||
|
export default {
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'fr'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Translate a doc
|
||||||
|
|
||||||
|
Copy the `docs/intro.md` file to the `i18n/fr` folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
|
||||||
|
|
||||||
|
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
|
||||||
|
|
||||||
|
## Start your localized site
|
||||||
|
|
||||||
|
Start your site on the French locale:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start -- --locale fr
|
||||||
|
```
|
||||||
|
|
||||||
|
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
In development, you can only use one locale at a time.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Add a Locale Dropdown
|
||||||
|
|
||||||
|
To navigate seamlessly across languages, add a locale dropdown.
|
||||||
|
|
||||||
|
Modify the `docusaurus.config.js` file:
|
||||||
|
|
||||||
|
```js title="docusaurus.config.js"
|
||||||
|
export default {
|
||||||
|
themeConfig: {
|
||||||
|
navbar: {
|
||||||
|
items: [
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
type: 'localeDropdown',
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The locale dropdown now appears in your navbar:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Build your localized site
|
||||||
|
|
||||||
|
Build your site for a specific locale:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build -- --locale fr
|
||||||
|
```
|
||||||
|
|
||||||
|
Or build your site to include all the locales at once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
153
lst-docs/docusaurus.config.ts
Normal file
153
lst-docs/docusaurus.config.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { themes as prismThemes } from "prism-react-renderer";
|
||||||
|
import type { Config } from "@docusaurus/types";
|
||||||
|
import type * as Preset from "@docusaurus/preset-classic";
|
||||||
|
|
||||||
|
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
title: "Logistics Support TOol",
|
||||||
|
tagline: "lst",
|
||||||
|
favicon: "img/favicon.ico",
|
||||||
|
|
||||||
|
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
||||||
|
future: {
|
||||||
|
v4: true, // Improve compatibility with the upcoming Docusaurus v4
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set the production url of your site here
|
||||||
|
url: "http://localhost:8080",
|
||||||
|
// Set the /<baseUrl>/ pathname under which your site is served
|
||||||
|
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||||
|
baseUrl: "/lst/docs/",
|
||||||
|
|
||||||
|
// GitHub pages deployment config.
|
||||||
|
// If you aren't using GitHub pages, you don't need these.
|
||||||
|
organizationName: "facebook", // Usually your GitHub org/user name.
|
||||||
|
projectName: "docusaurus", // Usually your repo name.
|
||||||
|
|
||||||
|
onBrokenLinks: "throw",
|
||||||
|
onBrokenMarkdownLinks: "warn",
|
||||||
|
|
||||||
|
// Even if you don't use internationalization, you can use this field to set
|
||||||
|
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||||
|
// may want to replace "en" with "zh-Hans".
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "en",
|
||||||
|
locales: ["en"],
|
||||||
|
},
|
||||||
|
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
"classic",
|
||||||
|
{
|
||||||
|
docs: {
|
||||||
|
sidebarPath: "./sidebars.ts",
|
||||||
|
// Please change this to your repo.
|
||||||
|
// Remove this to remove the "edit this page" links.
|
||||||
|
editUrl:
|
||||||
|
"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
|
||||||
|
},
|
||||||
|
blog: {
|
||||||
|
showReadingTime: true,
|
||||||
|
feedOptions: {
|
||||||
|
type: ["rss", "atom"],
|
||||||
|
xslt: true,
|
||||||
|
},
|
||||||
|
// Please change this to your repo.
|
||||||
|
// Remove this to remove the "edit this page" links.
|
||||||
|
editUrl:
|
||||||
|
"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
|
||||||
|
// Useful options to enforce blogging best practices
|
||||||
|
onInlineTags: "warn",
|
||||||
|
onInlineAuthors: "warn",
|
||||||
|
onUntruncatedBlogPosts: "warn",
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
customCss: "./src/css/custom.css",
|
||||||
|
},
|
||||||
|
} satisfies Preset.Options,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
themeConfig: {
|
||||||
|
// Replace with your project's social card
|
||||||
|
image: "img/docusaurus-social-card.jpg",
|
||||||
|
navbar: {
|
||||||
|
title: "Logistics Support Tool",
|
||||||
|
logo: {
|
||||||
|
alt: "lst",
|
||||||
|
src: "img/logo.svg",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: "docSidebar",
|
||||||
|
sidebarId: "tutorialSidebar",
|
||||||
|
position: "left",
|
||||||
|
label: "Tutorial",
|
||||||
|
},
|
||||||
|
{ to: "/blog", label: "Blog", position: "left" },
|
||||||
|
{
|
||||||
|
href: "https://github.com/facebook/docusaurus",
|
||||||
|
label: "GitHub",
|
||||||
|
position: "right",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
style: "dark",
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
title: "Docs",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Tutorial",
|
||||||
|
to: "/docs/intro",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Community",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Stack Overflow",
|
||||||
|
href: "https://stackoverflow.com/questions/tagged/docusaurus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Discord",
|
||||||
|
href: "https://discordapp.com/invite/docusaurus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "X",
|
||||||
|
href: "https://x.com/docusaurus",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "More",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Blog",
|
||||||
|
to: "/blog",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "GitHub",
|
||||||
|
href: "https://github.com/facebook/docusaurus",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
|
||||||
|
},
|
||||||
|
prism: {
|
||||||
|
theme: prismThemes.github,
|
||||||
|
darkTheme: prismThemes.dracula,
|
||||||
|
},
|
||||||
|
colorMode: {
|
||||||
|
defaultMode: "dark", // Set dark as default
|
||||||
|
disableSwitch: true, // Allow users to toggle
|
||||||
|
respectPrefersColorScheme: false, // Ignore OS preference
|
||||||
|
},
|
||||||
|
} satisfies Preset.ThemeConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
17474
lst-docs/package-lock.json
generated
Normal file
17474
lst-docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
lst-docs/package.json
Normal file
48
lst-docs/package.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "lst-docs",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"docusaurus": "docusaurus",
|
||||||
|
"start": "docusaurus start --no-open",
|
||||||
|
"build": "rimraf build && docusaurus build && npm run build:copy",
|
||||||
|
"build:copy": "powershell -File copy.ps1",
|
||||||
|
"swizzle": "docusaurus swizzle",
|
||||||
|
"deploy": "docusaurus deploy",
|
||||||
|
"clear": "docusaurus clear",
|
||||||
|
"serve": "docusaurus serve",
|
||||||
|
"write-translations": "docusaurus write-translations",
|
||||||
|
"write-heading-ids": "docusaurus write-heading-ids",
|
||||||
|
"typecheck": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/core": "3.8.1",
|
||||||
|
"@docusaurus/preset-classic": "3.8.1",
|
||||||
|
"@mdx-js/react": "^3.0.0",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@docusaurus/module-type-aliases": "3.8.1",
|
||||||
|
"@docusaurus/tsconfig": "3.8.1",
|
||||||
|
"@docusaurus/types": "3.8.1",
|
||||||
|
"typescript": "~5.6.2"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.5%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 3 chrome version",
|
||||||
|
"last 3 firefox version",
|
||||||
|
"last 5 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
lst-docs/sidebars.ts
Normal file
33
lst-docs/sidebars.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
|
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creating a sidebar enables you to:
|
||||||
|
- create an ordered group of docs
|
||||||
|
- render a sidebar for each doc of that group
|
||||||
|
- provide next/previous navigation
|
||||||
|
|
||||||
|
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||||
|
|
||||||
|
Create as many sidebars as you want.
|
||||||
|
*/
|
||||||
|
const sidebars: SidebarsConfig = {
|
||||||
|
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||||
|
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||||
|
|
||||||
|
// But you can create a sidebar manually
|
||||||
|
/*
|
||||||
|
tutorialSidebar: [
|
||||||
|
'intro',
|
||||||
|
'hello',
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Tutorial',
|
||||||
|
items: ['tutorial-basics/create-a-document'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sidebars;
|
||||||
71
lst-docs/src/components/HomepageFeatures/index.tsx
Normal file
71
lst-docs/src/components/HomepageFeatures/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import Heading from '@theme/Heading';
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
type FeatureItem = {
|
||||||
|
title: string;
|
||||||
|
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
|
||||||
|
description: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FeatureList: FeatureItem[] = [
|
||||||
|
{
|
||||||
|
title: 'Easy to Use',
|
||||||
|
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Docusaurus was designed from the ground up to be easily installed and
|
||||||
|
used to get your website up and running quickly.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Focus on What Matters',
|
||||||
|
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Docusaurus lets you focus on your docs, and we'll do the chores. Go
|
||||||
|
ahead and move your docs into the <code>docs</code> directory.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Powered by React',
|
||||||
|
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Extend or customize your website layout by reusing React. Docusaurus can
|
||||||
|
be extended while reusing the same header and footer.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function Feature({title, Svg, description}: FeatureItem) {
|
||||||
|
return (
|
||||||
|
<div className={clsx('col col--4')}>
|
||||||
|
<div className="text--center">
|
||||||
|
<Svg className={styles.featureSvg} role="img" />
|
||||||
|
</div>
|
||||||
|
<div className="text--center padding-horiz--md">
|
||||||
|
<Heading as="h3">{title}</Heading>
|
||||||
|
<p>{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HomepageFeatures(): ReactNode {
|
||||||
|
return (
|
||||||
|
<section className={styles.features}>
|
||||||
|
<div className="container">
|
||||||
|
<div className="row">
|
||||||
|
{FeatureList.map((props, idx) => (
|
||||||
|
<Feature key={idx} {...props} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
lst-docs/src/components/HomepageFeatures/styles.module.css
Normal file
11
lst-docs/src/components/HomepageFeatures/styles.module.css
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.features {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureSvg {
|
||||||
|
height: 200px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
30
lst-docs/src/css/custom.css
Normal file
30
lst-docs/src/css/custom.css
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Any CSS included here will be global. The classic template
|
||||||
|
* bundles Infima by default. Infima is a CSS framework designed to
|
||||||
|
* work well for content-centric websites.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* You can override the default Infima variables here. */
|
||||||
|
:root {
|
||||||
|
--ifm-color-primary: #2e8555;
|
||||||
|
--ifm-color-primary-dark: #29784c;
|
||||||
|
--ifm-color-primary-darker: #277148;
|
||||||
|
--ifm-color-primary-darkest: #205d3b;
|
||||||
|
--ifm-color-primary-light: #33925d;
|
||||||
|
--ifm-color-primary-lighter: #359962;
|
||||||
|
--ifm-color-primary-lightest: #3cad6e;
|
||||||
|
--ifm-code-font-size: 95%;
|
||||||
|
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||||
|
[data-theme='dark'] {
|
||||||
|
--ifm-color-primary: #25c2a0;
|
||||||
|
--ifm-color-primary-dark: #21af90;
|
||||||
|
--ifm-color-primary-darker: #1fa588;
|
||||||
|
--ifm-color-primary-darkest: #1a8870;
|
||||||
|
--ifm-color-primary-light: #29d5b0;
|
||||||
|
--ifm-color-primary-lighter: #32d8b4;
|
||||||
|
--ifm-color-primary-lightest: #4fddbf;
|
||||||
|
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
23
lst-docs/src/pages/index.module.css
Normal file
23
lst-docs/src/pages/index.module.css
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||||
|
* and scoped locally.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.heroBanner {
|
||||||
|
padding: 4rem 0;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 996px) {
|
||||||
|
.heroBanner {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user