Compare commits
11
Commits
bfaa5b3783
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7024d2d2f9 | ||
|
|
293d11be17 | ||
|
|
fe97818968 | ||
|
|
aab71bb425 | ||
|
|
7c35c9328b | ||
|
|
1a63f5736c | ||
|
|
240e38f3af | ||
|
|
d7358d875f | ||
|
|
9fc422eb02 | ||
|
|
29f788ed9a | ||
|
|
5924e7842e |
No files matched your search
@@ -0,0 +1,51 @@
|
|||||||
|
name: Build, Test and Push Flask App
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- release
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: '3.11'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Ansible
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y ansible openssh-client
|
||||||
|
|
||||||
|
- name: Set up SSH key
|
||||||
|
env:
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_RELEASE_KEY }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
printf '%s' "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/deploy_key
|
||||||
|
chmod 600 ~/.ssh/deploy_key
|
||||||
|
ssh-keygen -y -f ~/.ssh/deploy_key >/dev/null
|
||||||
|
|
||||||
|
- name: Trust server host key
|
||||||
|
run: |
|
||||||
|
ssh-keyscan -H "${{ secrets.RELEASE_SERVER_IP }}" >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Test SSH
|
||||||
|
run: |
|
||||||
|
ssh \
|
||||||
|
-i ~/.ssh/deploy_key \
|
||||||
|
-o StrictHostKeyChecking=yes \
|
||||||
|
"${{ secrets.RELEASE_SERVER_USER }}@${{ secrets.RELEASE_SERVER_IP }}" \
|
||||||
|
'echo SSH works'
|
||||||
|
|
||||||
|
- name: Run Ansible playbook
|
||||||
|
run: |
|
||||||
|
ansible-playbook \
|
||||||
|
-i "${{ secrets.RELEASE_SERVER_IP }}," \
|
||||||
|
ansible/restart_release_compose.yml \
|
||||||
|
--user "${{ secrets.RELEASE_SERVER_USER }}" \
|
||||||
|
--private-key ~/.ssh/deploy_key
|
||||||
+8
-8
@@ -5,6 +5,11 @@ WORKDIR /app
|
|||||||
|
|
||||||
ENV PUPPETEER_SKIP_DOWNLOAD=true
|
ENV PUPPETEER_SKIP_DOWNLOAD=true
|
||||||
|
|
||||||
|
ARG SITE_URL
|
||||||
|
ARG PRERENDER_API_URL
|
||||||
|
ENV SITE_URL=${SITE_URL}
|
||||||
|
ENV PRERENDER_API_URL=${PRERENDER_API_URL}
|
||||||
|
|
||||||
# Копируем зависимости
|
# Копируем зависимости
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
@@ -20,18 +25,13 @@ RUN node scripts/prerender.js
|
|||||||
# ========== СТАДИЯ 2: СЕРВЕР ==========
|
# ========== СТАДИЯ 2: СЕРВЕР ==========
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
ENV BACKEND_UPSTREAM=pvh-backend:80
|
||||||
|
|
||||||
# Копируем собранные файлы из стадии 1
|
# Копируем собранные файлы из стадии 1
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx/default.conf.template /etc/nginx/templates/default.conf.template
|
||||||
|
|
||||||
# Конфиг для SPA (чтобы react-router работал)
|
# Конфиг для SPA (чтобы react-router работал)
|
||||||
RUN echo 'server { \
|
|
||||||
listen 80; \
|
|
||||||
location / { \
|
|
||||||
root /usr/share/nginx/html; \
|
|
||||||
try_files $uri $uri/ /index.html; \
|
|
||||||
} \
|
|
||||||
}' > /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
- name: Restart Docker Compose services
|
||||||
|
hosts: all
|
||||||
|
# become: yes # <-- больше не нужно, т.к. пользователь уже имеет права Docker
|
||||||
|
tasks:
|
||||||
|
- name: docker compose down
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: docker compose down pvh-frontend
|
||||||
|
chdir: ~/web
|
||||||
|
- name: prune system
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: docker system prune -a -f
|
||||||
|
- name: docker compose up -d
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: docker compose up -d pvh-frontend
|
||||||
|
chdir: ~/web
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://${BACKEND_UPSTREAM}/;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
|
|
||||||
Sitemap: https://pvh.projects.rustprogersteam.ru/sitemap.xml
|
Sitemap: /sitemap.xml
|
||||||
@@ -3,10 +3,16 @@ import { resolve, dirname } from "path";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const prerenderApiUrl = process.env.PRERENDER_API_URL;
|
||||||
|
|
||||||
async function getProducts() {
|
async function getProducts() {
|
||||||
|
if (!prerenderApiUrl) {
|
||||||
|
console.warn('PRERENDER_API_URL is not configured; skipping product pre-rendering.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://pvh.projects.rustprogersteam.ru/api/productList');
|
const response = await fetch(`${prerenderApiUrl.replace(/\/$/, '')}/productList`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
@@ -215,7 +221,8 @@ ${urls.map(item => ` <url>
|
|||||||
</urlset>`;
|
</urlset>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sitemapContent = generateSitemap(urls, 'https://pvh.projects.rustprogersteam.ru');
|
const sitemapBaseUrl = process.env.SITE_URL?.replace(/\/$/, '') || 'http://localhost:5173';
|
||||||
|
const sitemapContent = generateSitemap(urls, sitemapBaseUrl);
|
||||||
const sitemapPath = resolve(distPath, 'sitemap.xml');
|
const sitemapPath = resolve(distPath, 'sitemap.xml');
|
||||||
writeFileSync(sitemapPath, sitemapContent);
|
writeFileSync(sitemapPath, sitemapContent);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ import axios, { AxiosInstance } from 'axios';
|
|||||||
class ApiClient {
|
class ApiClient {
|
||||||
private axiosInstance: AxiosInstance;
|
private axiosInstance: AxiosInstance;
|
||||||
|
|
||||||
constructor(baseURL: string = 'https://pvh.projects.rustprogersteam.ru/api') {
|
constructor(baseURL: string = import.meta.env.VITE_API_URL || '/api') {
|
||||||
this.axiosInstance = axios.create({
|
this.axiosInstance = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ gap: 15px;
|
|||||||
|
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 60px;
|
width: 200px;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
border-radius: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ text-align: center;
|
|||||||
|
|
||||||
@media (max-width: 700px){
|
@media (max-width: 700px){
|
||||||
.logo {
|
.logo {
|
||||||
width: 40px;
|
width: 100px;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
@@ -157,7 +156,7 @@ color: #fff;
|
|||||||
|
|
||||||
@media (max-width: 460px){
|
@media (max-width: 460px){
|
||||||
.logo {
|
.logo {
|
||||||
width: 40px;
|
width: 80px;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import styles from "./Footer.module.css";
|
import styles from "./Footer.module.css";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import blackLogo from "../../assets/images/BlackLogo.jpg";
|
import Logo from "../../assets/images/Logo.jpg";
|
||||||
import PriceLisForm from "../PriceLisForm/PriceLisForm";
|
import PriceLisForm from "../PriceLisForm/PriceLisForm";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import OrangeLogo from "../../assets/images/OrangeLogo.jpg";
|
|
||||||
|
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@@ -12,7 +11,7 @@ const Footer: React.FC = () => {
|
|||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.left}>
|
<div className={styles.left}>
|
||||||
<img src={blackLogo} alt="logo" className={styles.logo} />
|
<img src={Logo} alt="logo" className={styles.logo} />
|
||||||
<span className={styles.copy}>RK-Plastic Tech. 2021-2026 ©</span>
|
<span className={styles.copy}>RK-Plastic Tech. 2021-2026 ©</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ const Footer: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.right}>
|
<div className={styles.right}>
|
||||||
<div className={styles.phone}>+7 910 121 09 62</div>
|
<div className={styles.phone}>+7 831 410-90-89</div>
|
||||||
<a className={styles.mail}>rkplastic@mail.ru</a>
|
<a className={styles.mail}>rkplastic@mail.ru</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const dispatch = useAppDispatch();
|
|||||||
<div className={styles.headeLogo}>
|
<div className={styles.headeLogo}>
|
||||||
<a href="/" style={{padding: 5}}><img src={logo} alt="logo" className={styles.logoImage}/></a>
|
<a href="/" style={{padding: 5}}><img src={logo} alt="logo" className={styles.logoImage}/></a>
|
||||||
<div className={styles.phoneBlock}>
|
<div className={styles.phoneBlock}>
|
||||||
<a href="">+7 910 121 09 62</a>
|
<a href="">+7 831 410-90-89</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const QuestionsBlock: React.FC = () => {
|
|||||||
<PhoneOutlined className={styles.phoneIcon} />
|
<PhoneOutlined className={styles.phoneIcon} />
|
||||||
<div>
|
<div>
|
||||||
<a href="tel:+79101210962" className={styles.phoneNumber}>
|
<a href="tel:+79101210962" className={styles.phoneNumber}>
|
||||||
+7 910 121 09 62
|
+7 831 410-90-89
|
||||||
</a>
|
</a>
|
||||||
<div className={styles.phoneInfo}>
|
<div className={styles.phoneInfo}>
|
||||||
Понедельник - пятница, с 09:00 до 18:00 по Москве
|
Понедельник - пятница, с 09:00 до 18:00 по Москве
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const About = () => {
|
|||||||
Смотреть продукцию
|
Смотреть продукцию
|
||||||
</Link>
|
</Link>
|
||||||
<a href="tel:+79101210962" className={styles.secondaryLink}>
|
<a href="tel:+79101210962" className={styles.secondaryLink}>
|
||||||
+7 910 121 09 62
|
+7 831 410-90-89
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import styles from "./Contacts.module.css";
|
|||||||
|
|
||||||
const phones = [
|
const phones = [
|
||||||
{
|
{
|
||||||
label: "+7 910 121 09 62",
|
label: "+7 831 410-90-89",
|
||||||
href: "tel:+79101210962",
|
href: "tel:+78314109089",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -8,7 +8,7 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
Sitemap({
|
Sitemap({
|
||||||
hostname: 'https://pvh.projects.rustprogersteam.ru',
|
hostname: process.env.SITE_URL || 'http://localhost:5173',
|
||||||
exclude: ['/admin', '/profile'],
|
exclude: ['/admin', '/profile'],
|
||||||
readable: true,
|
readable: true,
|
||||||
outDir: './dist',
|
outDir: './dist',
|
||||||
@@ -18,12 +18,20 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0', // Слушаем все интерфейсы
|
host: '0.0.0.0', // Слушаем все интерфейсы
|
||||||
port: 5173,
|
port: 5173,
|
||||||
allowedHosts: ['pvh.projects.rustprogersteam.ru'],
|
allowedHosts: process.env.VITE_ALLOWED_HOSTS?.split(',').map((host) => host.trim()),
|
||||||
strictPort: true, // Запрещаем автоматический выбор порта
|
strictPort: true, // Запрещаем автоматический выбор порта
|
||||||
hmr: {
|
hmr: {
|
||||||
clientPort: 5173, // Важно для HMR в Docker
|
clientPort: 5173, // Важно для HMR в Docker
|
||||||
},
|
},
|
||||||
// Разрешаем CORS для внешних запросов
|
// Разрешаем CORS для внешних запросов
|
||||||
|
proxy: process.env.VITE_API_PROXY_TARGET
|
||||||
|
? {
|
||||||
|
'/api': {
|
||||||
|
target: process.env.VITE_API_PROXY_TARGET,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
cors: true
|
cors: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in new issue
Block a user