Compare commits

...

7 Commits

Author SHA1 Message Date
herel e4db258bc5 fix: update version 2025-12-10 11:38:16 +01:00
herel 449adc60e0 feat: dark mode and python 3.14 2025-12-10 11:36:40 +01:00
herel d68dc8411f chore: speedup readyness 2025-10-15 17:25:08 +02:00
herel 60f776c56d chore: service internal traffic policy 2025-10-15 17:24:13 +02:00
herel 7228dc4ec6 chore: add scale-to-zero overlay 2025-10-15 17:11:30 +02:00
herel e4989dcc8d chore: bump production image tag to 1.2.0 2025-10-07 08:47:52 +02:00
herel 3fdbfcf20e feat: replace checkbox with radio buttons for exercise type selection
- Replace the checkbox "Générer uniquement des multiplications" with radio buttons for "Multiplications uniquement" and "Multiplications ET divisions"
- Update JavaScript code to handle radio button values instead of checkbox
- Maintain backward compatibility with old localStorage format
- Ensure all functionality remains intact
2025-10-07 08:46:38 +02:00
9 changed files with 327 additions and 40 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
# Use Python 3.13 slim image as base
FROM python:3.13-slim AS builder
FROM python:3.14-slim AS builder
# Set working directory
WORKDIR /app
@@ -17,7 +17,7 @@ RUN --mount=type=cache,id=pip,target=/root/.cache/pip \
--target=/app/site-packages \
-r requirements.txt
FROM python:3.13-slim
FROM python:3.14-slim
COPY --from=builder /app/site-packages /app/site-packages
+204 -11
View File
@@ -5,17 +5,42 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Générateur d'Exercices de Mathématiques</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
--bs-border-color: #dee2e6;
--bs-secondary-bg: #f8f9fa;
--bs-tertiary-bg: #ffffff;
}
[data-bs-theme="dark"] {
--bs-body-bg: #121212;
--bs-body-color: #e9ecef;
--bs-border-color: #495057;
--bs-secondary-bg: #212529;
--bs-tertiary-bg: #343a40;
}
body {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.card {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transition: 0.3s;
margin-bottom: 20px;
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-border-color);
}
.card:hover {
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
.exercise-result {
background-color: #f8f9fa;
background-color: var(--bs-secondary-bg);
border-radius: 5px;
padding: 15px;
margin-top: 20px;
@@ -25,9 +50,81 @@
height: 3rem;
margin-right: 0.75rem;
}
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
border-radius: 50%;
width: 50px;
height: 50px;
border: none;
background-color: var(--bs-tertiary-bg);
color: var(--bs-body-color);
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.dark-mode-toggle:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
[data-bs-theme="dark"] .bg-light {
background-color: var(--bs-secondary-bg) !important;
}
[data-bs-theme="dark"] .text-muted {
color: var(--bs-body-color) !important;
opacity: 0.7;
}
[data-bs-theme="dark"] .table {
--bs-table-bg: var(--bs-tertiary-bg);
--bs-table-striped-bg: var(--bs-secondary-bg);
--bs-table-hover-bg: var(--bs-border-color);
}
[data-bs-theme="dark"] .alert {
border-color: var(--bs-border-color);
}
[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select {
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-border-color);
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .form-control:focus,
[data-bs-theme="dark"] .form-select:focus {
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-primary);
color: var(--bs-body-color);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
[data-bs-theme="dark"] .form-check-input {
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-border-color);
}
[data-bs-theme="dark"] .form-check-input:checked {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
.transition-all {
transition: all 0.3s ease;
}
</style>
</head>
<body>
<!-- Dark Mode Toggle Button -->
<button class="dark-mode-toggle" id="darkModeToggle" title="Basculer le mode sombre" aria-label="Basculer le mode sombre">
<i class="bi bi-moon-fill" id="darkModeIcon"></i>
</button>
<div class="container mt-5 mb-5">
<div class="row justify-content-center">
<div class="col-lg-8">
@@ -61,9 +158,20 @@
<div class="form-text">Nombre total d'exercices à générer (entre 1 et 100)</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="multiplicationOnly">
<label class="form-check-label" for="multiplicationOnly">Générer uniquement des multiplications</label>
<div class="mb-3">
<label class="form-label">Types d'exercices</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="exerciseType" id="multiplicationOnly" value="multiplication" checked>
<label class="form-check-label" for="multiplicationOnly">
Multiplications uniquement
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="exerciseType" id="multiplicationAndDivision" value="both">
<label class="form-check-label" for="multiplicationAndDivision">
Multiplications ET divisions
</label>
</div>
</div>
<div class="mb-3">
@@ -240,7 +348,7 @@
</div>
</div>
<footer class="bg-light text-center text-muted py-4 mt-5">
<footer class="bg-light text-center text-muted py-4 mt-5 transition-all">
<div class="container">
<p>Générateur d'Exercices de Mathématiques &copy; 2025</p>
</div>
@@ -248,6 +356,72 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Dark Mode Theme Management
class ThemeManager {
constructor() {
this.init();
}
init() {
// Load saved theme or detect system preference
const savedTheme = localStorage.getItem('theme');
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const theme = savedTheme || systemTheme;
this.setTheme(theme);
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
this.setTheme(e.matches ? 'dark' : 'light');
}
});
// Setup toggle button
this.setupToggleButton();
}
setTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('theme', theme);
this.updateToggleIcon(theme);
}
toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-bs-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
this.setTheme(newTheme);
}
updateToggleIcon(theme) {
const icon = document.getElementById('darkModeIcon');
if (theme === 'dark') {
icon.className = 'bi bi-sun-fill';
} else {
icon.className = 'bi bi-moon-fill';
}
}
setupToggleButton() {
const toggleButton = document.getElementById('darkModeToggle');
toggleButton.addEventListener('click', () => {
this.toggleTheme();
this.animateToggle();
});
}
animateToggle() {
const button = document.getElementById('darkModeToggle');
button.style.transform = 'scale(0.9)';
setTimeout(() => {
button.style.transform = '';
}, 150);
}
}
// Initialize theme manager
const themeManager = new ThemeManager();
// Current page for pagination
let currentPage = 1;
const pageSize = 10;
@@ -258,7 +432,7 @@
minTable: document.getElementById('minTable').value,
maxTable: document.getElementById('maxTable').value,
numExercises: document.getElementById('numExercises').value,
multiplicationOnly: document.getElementById('multiplicationOnly').checked,
exerciseType: document.querySelector('input[name="exerciseType"]:checked').value,
maxFirstOperand: document.getElementById('maxFirstOperand').value
};
@@ -279,7 +453,18 @@
document.getElementById('minTable').value = exerciseValues.minTable || 1;
document.getElementById('maxTable').value = exerciseValues.maxTable || 10;
document.getElementById('numExercises').value = exerciseValues.numExercises || 15;
document.getElementById('multiplicationOnly').checked = exerciseValues.multiplicationOnly || false;
// Restore radio button selection
if (exerciseValues.exerciseType) {
const radio = document.querySelector(`input[name="exerciseType"][value="${exerciseValues.exerciseType}"]`);
if (radio) {
radio.checked = true;
}
} else if (exerciseValues.multiplicationOnly !== undefined) {
// Handle legacy storage format
document.getElementById('multiplicationOnly').checked = exerciseValues.multiplicationOnly;
}
document.getElementById('maxFirstOperand').value = exerciseValues.maxFirstOperand || 10;
}
@@ -463,18 +648,25 @@
// Load PDF list on page load
document.addEventListener('DOMContentLoaded', function() {
restoreFormValues();
loadPdfList(currentPage);
// Ensure theme is initialized first
setTimeout(() => {
restoreFormValues();
loadPdfList(currentPage);
}, 100);
});
// Add event listeners to save form values on change
document.getElementById('minTable').addEventListener('change', saveFormValues);
document.getElementById('maxTable').addEventListener('change', saveFormValues);
document.getElementById('numExercises').addEventListener('change', saveFormValues);
document.getElementById('multiplicationOnly').addEventListener('change', saveFormValues);
document.getElementById('maxFirstOperand').addEventListener('change', saveFormValues);
document.getElementById('numOperationExercises').addEventListener('change', saveFormValues);
// Add event listeners for radio buttons
document.querySelectorAll('input[name="exerciseType"]').forEach(radio => {
radio.addEventListener('change', saveFormValues);
});
// Event listener for select all button
document.getElementById('selectAllBtn').addEventListener('click', function() {
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
@@ -577,7 +769,8 @@
const minTable = parseInt(document.getElementById('minTable').value);
const maxTable = parseInt(document.getElementById('maxTable').value);
const numExercises = parseInt(document.getElementById('numExercises').value);
const multiplicationOnly = document.getElementById('multiplicationOnly').checked;
const exerciseType = document.querySelector('input[name="exerciseType"]:checked').value;
const multiplicationOnly = (exerciseType === 'multiplication');
const maxFirstOperand = parseInt(document.getElementById('maxFirstOperand').value);
const generateBtn = document.getElementById('generateBtn');
+2 -2
View File
@@ -57,8 +57,8 @@ spec:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
initialDelaySeconds: 2
periodSeconds: 3
timeoutSeconds: 3
failureThreshold: 3
# Environment variables from ConfigMap
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: keda-add-ons-http-interceptor-proxy
name: keda-add-ons-http-interceptor-proxy
spec:
externalName: keda-add-ons-http-interceptor-proxy.keda
selector:
app: keda-add-ons-http-interceptor-proxy
type: ExternalName
status:
loadBalancer: {}
@@ -0,0 +1,24 @@
kind: HTTPScaledObject
apiVersion: http.keda.sh/v1alpha1
metadata:
name: math-exercises
spec:
hosts:
- "math-tables.cl1.parano.ch"
pathPrefixes:
- /
scaleTargetRef:
name: math-exercises-app
kind: Deployment
apiVersion: apps/v1
service: math-exercises-service
portName: http
replicas:
min: 0
max: 3
scaledownPeriod: 60
scalingMetric:
requestRate:
granularity: 1s
targetValue: 100
window: 1m
@@ -0,0 +1,51 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: math-tables
resources:
- ../production
- external-service.yaml
- http-scaled-object.yaml
patches:
- patch: |
[
{
"op": "remove",
"path": "/spec/replicas"
}
]
target:
group: apps
version: v1
kind: Deployment
name: math-exercises-app
- path: remove-pdb.yaml
- patch: |
[
{
"op": "replace",
"path": "/spec/rules/0/http/paths/0/backend/service/port/number",
"value": 8080
},
{
"op": "replace",
"path": "/spec/rules/0/http/paths/0/backend/service/name",
"value": "keda-add-ons-http-interceptor-proxy"
}
]
target:
group: networking.k8s.io
version: v1
kind: Ingress
name: math-exercises-ingress
- patch: |
[
{
"op": "replace",
"path": "/spec/internalTrafficPolicy",
"value": "Cluster"
}
]
target:
version: v1
kind: Service
name: math-exercises-service
@@ -0,0 +1,5 @@
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: math-exercises-pdb
$patch: delete
@@ -13,7 +13,7 @@ resources:
images:
- name: math-exercises
newName: harbor.cl1.parano.ch/library/math-exercice
newTag: 1.1.0
newTag: 1.4.0
# Production-specific labels
+24 -24
View File
@@ -1,32 +1,32 @@
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.10.0
boto3==1.26.160
boto3-stubs==1.40.23
botocore==1.29.165
botocore-stubs==1.40.23
click==8.2.1
anyio==4.12.0
boto3==1.42.6
boto3-stubs==1.42.6
botocore==1.42.6
botocore-stubs==1.42.6
click==8.3.1
defusedxml==0.7.1
fastapi==0.116.1
fonttools==4.59.2
fpdf2==2.8.4
fastapi==0.124.0
fonttools==4.61.0
fpdf2==2.8.5
h11==0.16.0
idna==3.10
Jinja2==3.1.4
idna==3.11
Jinja2==3.1.6
jmespath==1.0.1
MarkupSafe==3.0.2
pillow==11.3.0
pydantic==2.11.7
pydantic_core==2.33.2
MarkupSafe==3.0.3
pillow==12.0.0
pydantic==2.12.5
pydantic_core==2.41.5
python-dateutil==2.9.0.post0
python-multipart==0.0.9
s3transfer==0.6.2
python-multipart==0.0.20
s3transfer==0.16.0
six==1.17.0
sniffio==1.3.1
starlette==0.47.3
types-awscrt==0.27.6
types-fpdf2==2.8.4.20250822
types-s3transfer==0.13.1
typing-inspection==0.4.1
starlette==0.50.0
types-awscrt==0.29.2
types-s3transfer==0.16.0
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==1.26.20
uvicorn==0.30.6
urllib3==2.6.1
uvicorn==0.38.0