Init project
This commit is contained in:
45
frontend/Dockerfile
Normal file
45
frontend/Dockerfile
Normal file
@@ -0,0 +1,45 @@
|
||||
# Многоэтапная сборка для Angular приложения
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование package файлов
|
||||
COPY package*.json ./
|
||||
|
||||
# Установка зависимостей
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Копирование исходного кода
|
||||
COPY . .
|
||||
|
||||
# Сборка приложения
|
||||
RUN npm run build:prod
|
||||
|
||||
# Финальный образ с nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
# Копирование собранного приложения
|
||||
COPY --from=builder /app/dist/erp-mvp-frontend /usr/share/nginx/html
|
||||
|
||||
# Копирование конфигурации nginx
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Создание пользователя для безопасности
|
||||
RUN addgroup -g 1001 -S appgroup && \
|
||||
adduser -u 1001 -S appuser -G appgroup
|
||||
|
||||
# Смена владельца файлов
|
||||
RUN chown -R appuser:appgroup /usr/share/nginx/html && \
|
||||
chown -R appuser:appgroup /var/cache/nginx && \
|
||||
chown -R appuser:appgroup /var/log/nginx && \
|
||||
chown -R appuser:appgroup /etc/nginx/conf.d
|
||||
|
||||
# Переключение на непривилегированного пользователя
|
||||
USER appuser
|
||||
|
||||
# Экспорт порта
|
||||
EXPOSE 80
|
||||
|
||||
# Команда запуска
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
141
frontend/angular.json
Normal file
141
frontend/angular.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"erp-mvp-frontend": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss",
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:class": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:resolver": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"skipTests": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/erp-mvp-frontend",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "erp-mvp-frontend:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "erp-mvp-frontend:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "erp-mvp-frontend:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
91
frontend/nginx.conf
Normal file
91
frontend/nginx.conf
Normal file
@@ -0,0 +1,91 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Логирование
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
# Основные настройки
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
# Gzip сжатие
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/json
|
||||
application/javascript
|
||||
application/xml+rss
|
||||
application/atom+xml
|
||||
image/svg+xml;
|
||||
|
||||
# Основной сервер
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Безопасность
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
||||
|
||||
# Кэширование статических файлов
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Angular routing - fallback на index.html
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# API проксирование (опционально)
|
||||
location /api/ {
|
||||
proxy_pass http://core-service:8080;
|
||||
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;
|
||||
}
|
||||
|
||||
# Document Service проксирование
|
||||
location /doc-api/ {
|
||||
proxy_pass http://doc-service:8000/;
|
||||
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;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
frontend/package.json
Normal file
67
frontend/package.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "erp-mvp-frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "ERP для мастеров - Angular PWA Frontend",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"build:prod": "ng build --configuration production",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"pwa:build": "ng build --configuration production && ng add @angular/pwa"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.0.0",
|
||||
"@angular/common": "^17.0.0",
|
||||
"@angular/compiler": "^17.0.0",
|
||||
"@angular/core": "^17.0.0",
|
||||
"@angular/forms": "^17.0.0",
|
||||
"@angular/platform-browser": "^17.0.0",
|
||||
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||
"@angular/router": "^17.0.0",
|
||||
"@angular/service-worker": "^17.0.0",
|
||||
"@angular/material": "^17.0.0",
|
||||
"@angular/cdk": "^17.0.0",
|
||||
"@zxing/ngx-scanner": "^3.0.0",
|
||||
"@ngrx/store": "^17.0.0",
|
||||
"@ngrx/effects": "^17.0.0",
|
||||
"@ngrx/entity": "^17.0.0",
|
||||
"@ngrx/store-devtools": "^17.0.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.2",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"postcss": "^8.4.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.0.0",
|
||||
"@angular/cli": "^17.0.0",
|
||||
"@angular/compiler-cli": "^17.0.0",
|
||||
"@angular/language-service": "^17.0.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.0.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.2.2",
|
||||
"eslint": "^8.0.0",
|
||||
"@angular-eslint/eslint-plugin": "^17.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^17.0.0",
|
||||
"@angular-eslint/template-parser": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
}
|
||||
}
|
||||
100
frontend/src/app/app.module.ts
Normal file
100
frontend/src/app/app.module.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
// Angular Material
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
|
||||
// NgRx
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
|
||||
// QR Scanner
|
||||
import { ZXingScannerModule } from '@zxing/ngx-scanner';
|
||||
|
||||
// Components
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { HeaderComponent } from './components/header/header.component';
|
||||
import { QRScannerComponent } from './components/qr-scanner/qr-scanner.component';
|
||||
import { LocationDetailsComponent } from './components/location-details/location-details.component';
|
||||
import { ItemListComponent } from './components/item-list/item-list.component';
|
||||
import { SearchComponent } from './components/search/search.component';
|
||||
|
||||
// Services
|
||||
import { AuthInterceptor } from './services/auth.interceptor';
|
||||
|
||||
// Store
|
||||
import { appReducers } from './store/app.reducers';
|
||||
import { AppEffects } from './store/app.effects';
|
||||
|
||||
// Environment
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeaderComponent,
|
||||
QRScannerComponent,
|
||||
LocationDetailsComponent,
|
||||
ItemListComponent,
|
||||
SearchComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
ReactiveFormsModule,
|
||||
AppRoutingModule,
|
||||
|
||||
// Angular Material
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatSnackBarModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatDialogModule,
|
||||
MatListModule,
|
||||
MatChipsModule,
|
||||
MatBadgeModule,
|
||||
|
||||
// QR Scanner
|
||||
ZXingScannerModule,
|
||||
|
||||
// NgRx
|
||||
StoreModule.forRoot(appReducers),
|
||||
EffectsModule.forRoot([AppEffects]),
|
||||
StoreDevtoolsModule.instrument({
|
||||
maxAge: 25,
|
||||
logOnly: environment.production
|
||||
})
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true
|
||||
}
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
12
frontend/src/main.ts
Normal file
12
frontend/src/main.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
Reference in New Issue
Block a user