Compare commits
3 Commits
6e5f833d57
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1abcfc9c4c | |||
| d052ad4b52 | |||
| 5d81b0dbb0 |
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+58
@@ -0,0 +1,58 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mariadb:latest
|
||||||
|
container_name: db_container
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=rootpassword
|
||||||
|
- MYSQL_DATABASE=mydb
|
||||||
|
- MYSQL_USER=user
|
||||||
|
- MYSQL_PASSWORD=userpassword
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "mariadb -uroot -prootpassword -e 'SELECT 1' || exit 1"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: web_container
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- ./html:/usr/share/nginx/html:ro
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin:latest
|
||||||
|
container_name: phpmyadmin_container
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- PMA_HOST=db
|
||||||
|
ports:
|
||||||
|
- "8081:80"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "server.js"]
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "api-stock-it",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "server.js",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"mysql2": "^3.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const mysql = require('mysql2');
|
||||||
|
const cors = require('cors');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// Connexion à la base de données via variables d'environnement
|
||||||
|
const db = mysql.createPool({
|
||||||
|
host: process.env.DB_HOST || 'localhost',
|
||||||
|
user: process.env.DB_USER || 'root',
|
||||||
|
password: process.env.DB_PASSWORD || 'password',
|
||||||
|
database: process.env.DB_NAME || 'stock_it'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route GET à la racine : Health Check
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: 'OK',
|
||||||
|
message: 'L\'API de gestion de stock est bien en ligne et fonctionnelle !',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route GET : Récupérer tout le stock
|
||||||
|
app.get('/api/materiel', (req, res) => {
|
||||||
|
db.query('SELECT * FROM materiel', (err, results) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json(results);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route POST : Ajouter un nouvel équipement
|
||||||
|
app.post('/api/materiel', (req, res) => {
|
||||||
|
const { nom, quantite } = req.body;
|
||||||
|
db.query('INSERT INTO materiel (nom, quantite) VALUES (?, ?)', [nom, quantite], (err, results) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.status(201).json({ id: results.insertId, nom, quantite });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route PUT : Mettre à jour la quantité d'un équipement
|
||||||
|
app.put('/api/materiel/:id', (req, res) => {
|
||||||
|
const { quantite } = req.body;
|
||||||
|
db.query('UPDATE materiel SET quantite = ? WHERE id = ?', [quantite, req.params.id], (err, results) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ message: 'Quantité mise à jour avec succès' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route DELETE : Supprimer un équipement
|
||||||
|
app.delete('/api/materiel/:id', (req, res) => {
|
||||||
|
db.query('DELETE FROM materiel WHERE id = ?', [req.params.id], (err, results) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ message: 'Équipement supprimé avec succès' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const PORT = 3000;
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`API Backend démarrée sur le port ${PORT}`);
|
||||||
|
});
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: mariadb-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: db-init-script
|
||||||
|
data:
|
||||||
|
init.sql: |
|
||||||
|
CREATE DATABASE IF NOT EXISTS stock_it;
|
||||||
|
USE stock_it;
|
||||||
|
CREATE TABLE IF NOT EXISTS materiel (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
nom VARCHAR(255) NOT NULL,
|
||||||
|
quantite INT NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO materiel (nom, quantite) VALUES
|
||||||
|
('Serveur Dell PowerEdge', 3),
|
||||||
|
('Switch Cisco 24 ports', 5),
|
||||||
|
('Baie de brassage', 2);
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: db-backend
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mariadb
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: mariadb
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mariadb
|
||||||
|
image: mariadb:10.11
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
env:
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: db-credentials
|
||||||
|
key: root-password
|
||||||
|
- name: MYSQL_DATABASE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: app-config
|
||||||
|
key: db-name
|
||||||
|
ports:
|
||||||
|
- containerPort: 3306
|
||||||
|
volumeMounts:
|
||||||
|
- name: init-script-volume
|
||||||
|
mountPath: /docker-entrypoint-initdb.d/init.sql
|
||||||
|
subPath: init.sql
|
||||||
|
- name: mariadb-storage
|
||||||
|
mountPath: /var/lib/mysql
|
||||||
|
volumes:
|
||||||
|
- name: init-script-volume
|
||||||
|
configMap:
|
||||||
|
name: db-init-script
|
||||||
|
- name: mariadb-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: mariadb-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: db-service
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: mariadb
|
||||||
|
ports:
|
||||||
|
- port: 3306
|
||||||
|
targetPort: 3306
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: api-backend
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: node-api
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: node-api
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: node-api
|
||||||
|
image: gsz116/api-stock:v2
|
||||||
|
imagePullPolicy: Always
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: app-config
|
||||||
|
key: db-host
|
||||||
|
- name: DB_USER
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: app-config
|
||||||
|
key: db-user
|
||||||
|
- name: DB_NAME
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: app-config
|
||||||
|
key: db-name
|
||||||
|
- name: PORT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: app-config
|
||||||
|
key: backend-port
|
||||||
|
# Variable issue du Secret
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: db-credentials
|
||||||
|
key: root-password
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: api-service
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: node-api
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
nodePort: 30001
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: web-frontend
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx-front
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx-front
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx-front
|
||||||
|
image: gsz116/front-stock:v2
|
||||||
|
imagePullPolicy: Always
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
env:
|
||||||
|
- name: API_URL
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: app-config
|
||||||
|
key: api-url
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: frontend-service
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: nginx-front
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
nodePort: 30002
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
|
EXPOSE 80
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Gestion de Stock IT</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 40px;
|
||||||
|
background-color: #f4f4f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
background: #eee;
|
||||||
|
margin: 5px 0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Gestion de Stock Informatique</h1>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Ajouter un équipement</h2>
|
||||||
|
<form id="addForm">
|
||||||
|
<input type="text" id="nom" placeholder="Nom du matériel (ex: Routeur MicroTik)" required>
|
||||||
|
<input type="number" id="quantite" placeholder="Quantité" required min="1">
|
||||||
|
<button type="submit">Ajouter au stock</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Inventaire actuel</h2>
|
||||||
|
<ul id="stockList">Chargement du stock...</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const API_URL = 'http://192.168.1.15:30001/api/materiel';
|
||||||
|
|
||||||
|
async function fetchStock() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_URL);
|
||||||
|
const data = await response.json();
|
||||||
|
const list = document.getElementById('stockList');
|
||||||
|
list.innerHTML = '';
|
||||||
|
data.forEach(item => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = `
|
||||||
|
<span><strong>${item.nom}</strong></span>
|
||||||
|
<span>Quantité: ${item.quantite}</span>
|
||||||
|
<div>
|
||||||
|
<button onclick="modifierQuantite(${item.id}, ${item.quantite})" style="background-color: #ffc107; color: black; margin-right: 5px; width: auto;">Modifier</button>
|
||||||
|
<button onclick="supprimerMateriel(${item.id})" style="background-color: #dc3545; width: auto;">Supprimer</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
document.getElementById('stockList').innerHTML = '<li style="color:red;">Erreur de connexion à l\'API</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('addForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const nom = document.getElementById('nom').value;
|
||||||
|
const quantite = document.getElementById('quantite').value;
|
||||||
|
|
||||||
|
await fetch(API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ nom, quantite })
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('nom').value = '';
|
||||||
|
document.getElementById('quantite').value = '';
|
||||||
|
fetchStock();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nouvelle fonction : Modifier la quantité
|
||||||
|
window.modifierQuantite = async (id, ancienneQuantite) => {
|
||||||
|
const nouvelleQuantite = prompt("Entrez la nouvelle quantité :", ancienneQuantite);
|
||||||
|
|
||||||
|
// Si l'utilisateur n'a pas annulé et a entré un nombre valide
|
||||||
|
if (nouvelleQuantite !== null && nouvelleQuantite !== "" && !isNaN(nouvelleQuantite)) {
|
||||||
|
await fetch(`${API_URL}/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ quantite: nouvelleQuantite })
|
||||||
|
});
|
||||||
|
fetchStock(); // Rafraîchit la liste
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Nouvelle fonction : Supprimer un matériel
|
||||||
|
window.supprimerMateriel = async (id) => {
|
||||||
|
if (confirm("Êtes-vous sûr de vouloir supprimer ce matériel de l'inventaire ?")) {
|
||||||
|
await fetch(`${API_URL}/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
fetchStock(); // Rafraîchit la liste
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Charger le stock au démarrage
|
||||||
|
fetchStock();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user