Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a8cbf7d8f | |||
| 18e45644d5 | |||
| a835e9ca58 | |||
| 642ef2133a | |||
| b49ee9c524 | |||
| 684f7af92b | |||
| 25e9a86e55 | |||
| b9ca40735d | |||
| 49a297108e |
110
.drone.yml
110
.drone.yml
@ -1,89 +1,43 @@
|
|||||||
|
## Universal .drone.yml for all project types
|
||||||
|
## Configure your project via service.yaml (see ci-templates/docs/requirements.md)
|
||||||
|
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: kubernetes
|
type: kubernetes
|
||||||
name: deploy-frontend
|
name: ci
|
||||||
|
|
||||||
# Триггеры: запускать на push в ветки main и develop
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# --- Шаг 1: Сборка и отправка образа в Harbor ---
|
- name: prepare
|
||||||
# Этот шаг выполняется для любой из веток (main или develop)
|
image: alpine:3.19
|
||||||
- name: build-and-push
|
environment:
|
||||||
image: plugins/docker
|
GITEA_TOKEN:
|
||||||
settings:
|
from_secret: GITEA_TOKEN
|
||||||
# Укажите ваш домен Harbor
|
commands:
|
||||||
registry: registry.vigdorov.ru
|
- apk add --no-cache git bash yq
|
||||||
# Имя репозитория в Harbor (например, в проекте library)
|
- git clone --depth 1 https://token:$GITEA_TOKEN@git.vigdorov.ru/vigdorov/ci-templates.git .ci
|
||||||
repo: registry.vigdorov.ru/library/examples-for-kids-app
|
- chmod +x .ci/scripts/*.sh
|
||||||
# Тег будет равен первым 7 символам хеша коммита (например, a1b2c3d)
|
- bash .ci/scripts/prepare.sh
|
||||||
tags:
|
|
||||||
- ${DRONE_COMMIT_SHA:0:7}
|
- name: build
|
||||||
# Используем секреты, которые мы создали в Drone
|
image: gcr.io/kaniko-project/executor:v1.23.2-debug
|
||||||
username:
|
depends_on: [prepare]
|
||||||
|
environment:
|
||||||
|
HARBOR_USER:
|
||||||
from_secret: HARBOR_USER
|
from_secret: HARBOR_USER
|
||||||
password:
|
HARBOR_PASSWORD:
|
||||||
from_secret: HARBOR_PASSWORD
|
from_secret: HARBOR_PASSWORD
|
||||||
|
commands:
|
||||||
|
- /busybox/sh .ci/scripts/build.sh
|
||||||
|
|
||||||
# --- Шаг 2: Развертывание в DEV-окружение ---
|
- name: deploy
|
||||||
- name: deploy-dev
|
image: alpine:3.19
|
||||||
image: alpine/k8s:1.28.2 # Образ с kubectl и другими утилитами
|
depends_on: [build]
|
||||||
# Запускать этот шаг ТОЛЬКО для ветки 'develop'
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- develop
|
|
||||||
environment:
|
environment:
|
||||||
# Используем секрет с kubeconfig
|
KUBE_CONFIG:
|
||||||
KUBECONFIG:
|
|
||||||
from_secret: KUBE_CONFIG
|
from_secret: KUBE_CONFIG
|
||||||
commands:
|
commands:
|
||||||
# Готовим переменные для dev-окружения
|
- apk add --no-cache bash yq kubectl helm
|
||||||
- 'export APP_NAMESPACE="dev-ns"' # Будем деплоить в отдельный неймспейс
|
- bash .ci/scripts/deploy.sh
|
||||||
- 'export HOSTNAME="dev_examples-for-kids.vigdorov.ru"'
|
|
||||||
- 'export IMAGE_TAG="${DRONE_COMMIT_SHA:0:7}"'
|
|
||||||
- 'export IMAGE_NAME="ci.vigdorov.ru/library/examples-for-kids-app"'
|
|
||||||
- 'export SECRET_NAME="wildcard-cert"'
|
|
||||||
# Создаем неймспейс, если его нет
|
|
||||||
- 'kubectl create namespace $APP_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -'
|
|
||||||
# Заменяем метки в шаблонах на реальные значения и применяем
|
|
||||||
- 'sed -e "s|__IMAGE__|$IMAGE_NAME:$IMAGE_TAG|g" k8s/deployment.yaml | kubectl apply -n $APP_NAMESPACE -f -'
|
|
||||||
- 'kubectl apply -n $APP_NAMESPACE -f k8s/service.yaml'
|
|
||||||
- 'sed -e "s|__HOSTNAME__|$HOSTNAME|g" -e "s|__SECRET_NAME__|$SECRET_NAME|g" k8s/ingress.yaml | kubectl apply -n $APP_NAMESPACE -f -'
|
|
||||||
- 'echo "Deployed to DEV: https://$HOSTNAME"'
|
|
||||||
|
|
||||||
# --- Шаг 3: Развертывание в PROD-окружение ---
|
trigger:
|
||||||
- name: deploy-prod
|
branch: [master, develop]
|
||||||
image: alpine/k8s:1.28.2
|
event: [push]
|
||||||
# Запускать этот шаг ТОЛЬКО для ветки 'main'
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
environment:
|
|
||||||
KUBE_CONFIG_CONTENT:
|
|
||||||
from_secret: KUBE_CONFIG
|
|
||||||
commands:
|
|
||||||
# Создаем kubeconfig файл из секрета
|
|
||||||
- 'mkdir -p ~/.kube'
|
|
||||||
- 'echo "$KUBE_CONFIG_CONTENT" > ~/.kube/config'
|
|
||||||
- 'chmod 600 ~/.kube/config'
|
|
||||||
# Заменяем localhost на внешний IP сервера
|
|
||||||
- 'sed -i "s|https://127.0.0.1:6443|https://192.168.1.55:6443|g" ~/.kube/config'
|
|
||||||
# Готовим переменные для prod-окружения
|
|
||||||
- 'export APP_NAMESPACE="prod-ns"'
|
|
||||||
- 'export HOSTNAME="examples-for-kids.vigdorov.ru"'
|
|
||||||
- 'export IMAGE_TAG="${DRONE_COMMIT_SHA:0:7}"'
|
|
||||||
- 'export IMAGE_NAME="registry.vigdorov.ru/library/examples-for-kids-app"'
|
|
||||||
- 'export SECRET_NAME="wildcard-cert"'
|
|
||||||
# Проверяем подключение к кластеру
|
|
||||||
- 'kubectl cluster-info'
|
|
||||||
# Создаем неймспейс
|
|
||||||
- 'kubectl create namespace $APP_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -'
|
|
||||||
# Разворачиваем приложение
|
|
||||||
- 'sed -e "s|__IMAGE__|$IMAGE_NAME:$IMAGE_TAG|g" k8s/deployment.yaml | kubectl apply -n $APP_NAMESPACE -f -'
|
|
||||||
- 'kubectl apply -n $APP_NAMESPACE -f k8s/service.yaml'
|
|
||||||
- 'sed -e "s|__HOSTNAME__|$HOSTNAME|g" -e "s|__SECRET_NAME__|$SECRET_NAME|g" k8s/ingress.yaml | kubectl apply -n $APP_NAMESPACE -f -'
|
|
||||||
- 'echo "Deployed to PROD: https://$HOSTNAME"'
|
|
||||||
|
|||||||
37
.github/workflows/deploy.yml
vendored
37
.github/workflows/deploy.yml
vendored
@ -1,37 +0,0 @@
|
|||||||
# This is a basic workflow to help you get started with Actions
|
|
||||||
|
|
||||||
name: Deploy to pages
|
|
||||||
|
|
||||||
# Controls when the action will run.
|
|
||||||
on:
|
|
||||||
# Triggers the workflow on push or pull request events but only for the master branch
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
||||||
jobs:
|
|
||||||
# This workflow contains a single job called "build"
|
|
||||||
build:
|
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
|
|
||||||
run: |
|
|
||||||
npm i
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
- name: Deploy 🚀
|
|
||||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
|
||||||
with:
|
|
||||||
ACCESS_TOKEN: ${{ secrets.GIT_TOKEN_V1 }}
|
|
||||||
BRANCH: gh-pages # The branch the action should deploy to.
|
|
||||||
FOLDER: build # The folder the action should deploy.
|
|
||||||
CLEAN: true # Automatically remove deleted files from the deploy branch
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
/.vscode
|
/.vscode
|
||||||
/build
|
/dist
|
||||||
/coverage
|
/coverage
|
||||||
**/junit.xml
|
**/junit.xml
|
||||||
.dependencies-cache
|
.dependencies-cache
|
||||||
|
|||||||
42
CLAUDE.md
Normal file
42
CLAUDE.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Examples for Kids
|
||||||
|
|
||||||
|
Веб-приложение для детей — тренажер решения математических примеров.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Интерактивный веб-интерфейс для практики арифметики. Дети решают примеры, получают обратную связь.
|
||||||
|
|
||||||
|
## Стек
|
||||||
|
|
||||||
|
- **Frontend:** Vanilla JS + Webpack
|
||||||
|
- **Styling:** Bootstrap 5
|
||||||
|
- **Testing:** Jest
|
||||||
|
- **Build:** Webpack 5
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install # Установка зависимостей
|
||||||
|
npm run dev # Запуск dev-сервера
|
||||||
|
npm run build # Production сборка
|
||||||
|
npm run test # Запуск тестов
|
||||||
|
npm run lint # Проверка линтером
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
examples-for-kids/
|
||||||
|
├── src/ # Исходный код
|
||||||
|
├── public/ # Статические файлы
|
||||||
|
├── coverage/ # Отчеты покрытия тестами
|
||||||
|
├── service.yaml # Конфиг для ci-templates
|
||||||
|
└── .drone.yml # CI/CD пайплайн (универсальный)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Деплой
|
||||||
|
|
||||||
|
- Тип: `web-frontend` (ci-templates)
|
||||||
|
- Dockerfile, nginx.conf, Helm chart — предоставляются ci-templates автоматически
|
||||||
|
- Конфигурация через `service.yaml`
|
||||||
|
- Домен: examples-for-kids.vigdorov.ru
|
||||||
14
Dockerfile
14
Dockerfile
@ -1,14 +0,0 @@
|
|||||||
# build environment
|
|
||||||
FROM node:18 as builder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm ci
|
|
||||||
COPY . .
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# production environment
|
|
||||||
FROM nginx:alpine
|
|
||||||
COPY --from=builder /app/build /usr/share/nginx/html
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
|
||||||
EXPOSE 80
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
frontend:
|
|
||||||
image: examples-for-kids:${BUILD_NUMBER:-latest}
|
|
||||||
ports:
|
|
||||||
- "3000:80"
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
frontend:
|
|
||||||
image: examples-for-kids:${BUILD_NUMBER:-latest}
|
|
||||||
ports:
|
|
||||||
- "3001:80"
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: examples-for-kids-app
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: examples-for-kids-app
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: examples-for-kids-app
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: harbor-creds # Имя секрета, который мы создали на Шаге 1
|
|
||||||
containers:
|
|
||||||
- name: examples-for-kids-app
|
|
||||||
# __IMAGE__ - это метка, которую заменит Drone
|
|
||||||
image: __IMAGE__
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: examples-for-kids-ingress
|
|
||||||
spec:
|
|
||||||
ingressClassName: traefik
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
# __HOSTNAME__ - метка для домена
|
|
||||||
- __HOSTNAME__
|
|
||||||
# __SECRET_NAME__ - метка для имени секрета с сертификатом
|
|
||||||
secretName: __SECRET_NAME__
|
|
||||||
rules:
|
|
||||||
- host: __HOSTNAME__
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: examples-for-kids-app-service
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: examples-for-kids-app-service
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: examples-for-kids-app
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 80
|
|
||||||
targetPort: 80
|
|
||||||
29
nginx.conf
29
nginx.conf
@ -1,29 +0,0 @@
|
|||||||
events {}
|
|
||||||
|
|
||||||
http {
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
|
|
||||||
location /stub_status {
|
|
||||||
stub_status on;
|
|
||||||
allow all;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
try_files $uri $uri/ $uri.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /assets/ {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~* \.(js|css|ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$ {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<body class="h-100">
|
<body class="h-100">
|
||||||
<div class="container h-100">
|
<div class="container h-100">
|
||||||
|
<div id="dev-marker" class="d-flex justify-content-start pt-3"></div>
|
||||||
<div class="d-flex justify-content-end pt-3">
|
<div class="d-flex justify-content-end pt-3">
|
||||||
<button class="btn btn-primary" type="button" id="reset-button"><i class="fas fa-sync"></i> Сбросить</button>
|
<button class="btn btn-primary" type="button" id="reset-button"><i class="fas fa-sync"></i> Сбросить</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
7
service.yaml
Normal file
7
service.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
service:
|
||||||
|
name: examples-for-kids
|
||||||
|
type: web-frontend
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
namespace: examples-for-kids
|
||||||
|
domain: examples-for-kids.vigdorov.ru
|
||||||
@ -127,7 +127,6 @@ const getExample = () => {
|
|||||||
default:
|
default:
|
||||||
return isPlusMinusKind ? getMinusExample() : getDivisionExample();
|
return isPlusMinusKind ? getMinusExample() : getDivisionExample();
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDifficultyById = id => {
|
export const getDifficultyById = id => {
|
||||||
@ -323,3 +322,13 @@ resetButton.addEventListener(EVENTS.CLICK, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
renderPage();
|
renderPage();
|
||||||
|
|
||||||
|
const markDev = () => {
|
||||||
|
if (['localhost', 'dev'].some(v => location.href.includes(v))) {
|
||||||
|
const marker = document.getElementById('dev-marker');
|
||||||
|
|
||||||
|
marker.innerText = 'DEVELOP';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
markDev();
|
||||||
|
|||||||
@ -8,7 +8,7 @@ module.exports = {
|
|||||||
entry: './src/script.js',
|
entry: './src/script.js',
|
||||||
output: {
|
output: {
|
||||||
filename: 'main.js',
|
filename: 'main.js',
|
||||||
path: path.resolve(__dirname, 'build'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
open: true,
|
open: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user