versioning
This commit is contained in:
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
@@ -10,6 +10,7 @@ pipeline {
|
|||||||
VUE_CONTAINER_NAME = "chore-app-frontend"
|
VUE_CONTAINER_NAME = "chore-app-frontend"
|
||||||
FLASK_CONTAINER_NAME = "chore-app-backend"
|
FLASK_CONTAINER_NAME = "chore-app-backend"
|
||||||
NETWORK_NAME = "chore-app-net"
|
NETWORK_NAME = "chore-app-net"
|
||||||
|
BASE_VERSION = '1.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
@@ -32,8 +33,7 @@ pipeline {
|
|||||||
stage('Build Backend (Flask) App') {
|
stage('Build Backend (Flask) App') {
|
||||||
steps {
|
steps {
|
||||||
dir('.') {
|
dir('.') {
|
||||||
sh 'docker build -t ${BACKEND_IMAGE} .'
|
sh """docker build --build-arg APP_BUILD=${BUILD_NUMBER} -t chore-app-backend:${BASE_VERSION} -t chore-app-backend:${BASE_VERSION}-${BUILD_NUMBER} -t chore-app-backend:latest ."""
|
||||||
sh 'docker tag ${BACKEND_IMAGE} ${BACKEND_IMAGE_LATEST}'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,7 @@ pipeline {
|
|||||||
docker run -d \\
|
docker run -d \\
|
||||||
--name ${FLASK_CONTAINER_NAME} \\
|
--name ${FLASK_CONTAINER_NAME} \\
|
||||||
--network ${NETWORK_NAME} \\
|
--network ${NETWORK_NAME} \\
|
||||||
|
-e BUILD_NUMBER=${BUILD_NUMBER} \\
|
||||||
-v ${FLASK_CONTAINER_NAME}_data:/app/data \\
|
-v ${FLASK_CONTAINER_NAME}_data:/app/data \\
|
||||||
${BACKEND_IMAGE_LATEST}
|
${BACKEND_IMAGE_LATEST}
|
||||||
"""
|
"""
|
||||||
|
|||||||
12
config/version.py
Normal file
12
config/version.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# python
|
||||||
|
# file: config/version.py
|
||||||
|
import os
|
||||||
|
|
||||||
|
BASE_VERSION = "1.0.0" # update manually when releasing features
|
||||||
|
|
||||||
|
def get_full_version() -> str:
|
||||||
|
"""
|
||||||
|
Return semantic version with optional Jenkins build metadata, e.g. 1.2.3+build.456.
|
||||||
|
"""
|
||||||
|
build = os.environ.get("BUILD_NUMBER") or os.environ.get("APP_BUILD")
|
||||||
|
return f"{BASE_VERSION}+build.{build}" if build else BASE_VERSION
|
||||||
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
chore-app-backend:
|
||||||
|
image: devserver.lan:5900/chore-app-backend:production
|
||||||
|
container_name: chore-app-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
expose:
|
||||||
|
- "5000"
|
||||||
|
networks:
|
||||||
|
- chore-app-net
|
||||||
|
volumes:
|
||||||
|
- chore-app-backend-data:/app/data # persists backend data
|
||||||
|
|
||||||
|
chore-app-frontend:
|
||||||
|
image: devserver.lan:5900/chore-app-frontend:production
|
||||||
|
container_name: chore-app-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "4600:443"
|
||||||
|
networks:
|
||||||
|
- chore-app-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
chore-app-net:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
chore-app-backend-data: {}
|
||||||
7
main.py
7
main.py
@@ -1,12 +1,13 @@
|
|||||||
import sys, logging, os
|
import sys, logging, os
|
||||||
from config.paths import get_user_image_dir
|
from config.paths import get_user_image_dir
|
||||||
from flask import Flask, request
|
from flask import Flask, request, jsonify
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
||||||
from api.child_api import child_api
|
from api.child_api import child_api
|
||||||
from api.image_api import image_api
|
from api.image_api import image_api
|
||||||
from api.reward_api import reward_api
|
from api.reward_api import reward_api
|
||||||
from api.task_api import task_api
|
from api.task_api import task_api
|
||||||
|
from config.version import get_full_version
|
||||||
from events.broadcaster import Broadcaster
|
from events.broadcaster import Broadcaster
|
||||||
from events.sse import sse_response_for_user, send_to_user
|
from events.sse import sse_response_for_user, send_to_user
|
||||||
from db.default import initializeImages
|
from db.default import initializeImages
|
||||||
@@ -29,6 +30,10 @@ app.register_blueprint(task_api)
|
|||||||
app.register_blueprint(image_api)
|
app.register_blueprint(image_api)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
|
@app.route("/version")
|
||||||
|
def api_version():
|
||||||
|
return jsonify({"version": get_full_version()})
|
||||||
|
|
||||||
@app.route("/events")
|
@app.route("/events")
|
||||||
def events():
|
def events():
|
||||||
# Authenticate user or read a token
|
# Authenticate user or read a token
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { computed } from 'vue'
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import LoginButton from '../components/LoginButton.vue'
|
import LoginButton from '../components/LoginButton.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -23,6 +23,20 @@ const showBack = computed(
|
|||||||
route.name === 'NotificationView'
|
route.name === 'NotificationView'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Version fetching
|
||||||
|
const appVersion = ref('')
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/version')
|
||||||
|
if (resp.ok) {
|
||||||
|
const data = await resp.json()
|
||||||
|
appVersion.value = data.version || ''
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
appVersion.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -142,6 +156,8 @@ const showBack = computed(
|
|||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<router-view />
|
<router-view />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<div v-if="appVersion" class="app-version">v{{ appVersion }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -251,6 +267,19 @@ const showBack = computed(
|
|||||||
align-self: start;
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-version {
|
||||||
|
position: fixed;
|
||||||
|
right: 18px;
|
||||||
|
bottom: 12px;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
color: #cbd5e1; /* Brighter slate-200 */
|
||||||
|
opacity: 0.85;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.back-btn {
|
.back-btn {
|
||||||
padding: 0.45rem 0.75rem;
|
padding: 0.45rem 0.75rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user