Compare commits
5 Commits
16ce1da9a2
...
8672ca7112
| Author | SHA1 | Date |
|---|---|---|
|
|
8672ca7112 | |
|
|
8abfff99e0 | |
|
|
57cf65b9a6 | |
|
|
4074d45384 | |
|
|
dadb6f3bcb |
3
.env
3
.env
|
|
@ -24,3 +24,6 @@
|
||||||
# COOKIE_SECURE=false
|
# COOKIE_SECURE=false
|
||||||
# COOKIE_SAME_SITE=lax
|
# COOKIE_SAME_SITE=lax
|
||||||
|
|
||||||
|
# Для меню
|
||||||
|
#RANGES_API_URL=http://192.168.2.39:9999
|
||||||
|
#RANGES_API_ENDPOINT=/api/ranges/9999
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Controller, Get, Param, Post, Body, Put } from '@nestjs/common';
|
import { Controller, Get, Post, Put, Body, Param, Headers, HttpException, HttpStatus } from '@nestjs/common';
|
||||||
import { MenuService } from './menu.service';
|
import { MenuService } from './menu.service';
|
||||||
import { MenuItem } from './menu.interface';
|
import { MenuItem } from './menu.interface';
|
||||||
|
|
||||||
|
|
@ -6,9 +6,35 @@ import { MenuItem } from './menu.interface';
|
||||||
export class MenuController {
|
export class MenuController {
|
||||||
constructor(private readonly menuService: MenuService) { }
|
constructor(private readonly menuService: MenuService) { }
|
||||||
|
|
||||||
@Get()
|
@Get('full')
|
||||||
async getMenu(): Promise<MenuItem> {
|
async getFullMenu(@Headers('if-modified-since') ifModifiedSince?: string) {
|
||||||
return this.menuService.getFullMenu();
|
try {
|
||||||
|
const result = await this.menuService.getFullMenuWithCache(ifModifiedSince);
|
||||||
|
|
||||||
|
if (!result.fresh && ifModifiedSince) {
|
||||||
|
throw new HttpException('Not Modified', HttpStatus.NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.menu;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status === HttpStatus.NOT_MODIFIED) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Failed to load menu',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('check-updates')
|
||||||
|
async checkUpdates(@Headers('if-modified-since') ifModifiedSince: string) {
|
||||||
|
if (!ifModifiedSince) {
|
||||||
|
throw new HttpException('If-Modified-Since header is required', HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasUpdates = await this.menuService.checkForUpdates(ifModifiedSince);
|
||||||
|
return { hasUpdates };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('save')
|
@Post('save')
|
||||||
|
|
@ -17,21 +43,18 @@ export class MenuController {
|
||||||
return { status: 'saved' };
|
return { status: 'saved' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('full')
|
|
||||||
async getFullMenu(): Promise<MenuItem> {
|
|
||||||
return this.menuService.getFullMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('overrides')
|
@Post('overrides')
|
||||||
async saveOverrides(@Body() data: { overrides: Partial<MenuItem>[] }) {
|
async saveOverrides(@Body() data: { overrides: Partial<MenuItem>[] }) {
|
||||||
return this.menuService.saveOverrides(data.overrides);
|
await this.menuService.saveOverrides(data.overrides);
|
||||||
|
return { status: 'success' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
async updateMenuItem(
|
async updateMenuItem(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() update: Partial<MenuItem>
|
@Body() update: Partial<MenuItem>
|
||||||
): Promise<MenuItem> {
|
) {
|
||||||
return this.menuService.updateMenuItem(id, update);
|
const updatedItem = await this.menuService.updateMenuItem(id, update);
|
||||||
|
return updatedItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,11 @@ import { HttpModule } from '@nestjs/axios';
|
||||||
import { MenuService } from './menu.service';
|
import { MenuService } from './menu.service';
|
||||||
import { PrometheusModule } from '../prometheus.module';
|
import { PrometheusModule } from '../prometheus.module';
|
||||||
import { RangeService } from './range.service';
|
import { RangeService } from './range.service';
|
||||||
|
import { RangeController } from './range.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrometheusModule, HttpModule],
|
imports: [PrometheusModule, HttpModule],
|
||||||
controllers: [MenuController],
|
controllers: [MenuController, RangeController],
|
||||||
providers: [MenuService, RangeService]
|
providers: [MenuService, RangeService]
|
||||||
})
|
})
|
||||||
export class MenuModule { }
|
export class MenuModule { }
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
||||||
import { PrometheusService } from '../prometheus.service';
|
import { PrometheusService } from '../prometheus.service';
|
||||||
import { MenuItem } from './menu.interface';
|
import { MenuItem } from './menu.interface';
|
||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
|
|
@ -7,6 +7,10 @@ import { RangeService } from './range.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MenuService {
|
export class MenuService {
|
||||||
|
private menuCache: MenuItem | null = null;
|
||||||
|
private lastModified: Date | null = null;
|
||||||
|
private cacheInitialized = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prometheusService: PrometheusService,
|
private readonly prometheusService: PrometheusService,
|
||||||
private readonly rangeService: RangeService
|
private readonly rangeService: RangeService
|
||||||
|
|
@ -15,16 +19,33 @@ export class MenuService {
|
||||||
private readonly menuOverridesPath = path.join(process.cwd(), 'data', 'menu.json');
|
private readonly menuOverridesPath = path.join(process.cwd(), 'data', 'menu.json');
|
||||||
|
|
||||||
async saveMenuToFile(): Promise<void> {
|
async saveMenuToFile(): Promise<void> {
|
||||||
const menu = await this.getFullMenu();
|
const { menu } = await this.getFullMenuWithCache();
|
||||||
await fs.mkdir(path.dirname(this.menuOverridesPath), { recursive: true });
|
await fs.mkdir(path.dirname(this.menuOverridesPath), { recursive: true });
|
||||||
await fs.writeFile(this.menuOverridesPath, JSON.stringify(menu, null, 2), 'utf-8');
|
await fs.writeFile(this.menuOverridesPath, JSON.stringify(menu, null, 2), 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFullMenu(): Promise<MenuItem> {
|
async getFullMenuWithCache(ifModifiedSince?: string): Promise<{ menu: MenuItem; fresh: boolean }> {
|
||||||
|
if (this.menuCache && this.lastModified && (!ifModifiedSince || new Date(ifModifiedSince) >= this.lastModified)) {
|
||||||
|
return { menu: this.menuCache, fresh: false };
|
||||||
|
}
|
||||||
|
|
||||||
const dynamicItemsPromise = this.generateDynamicItems();
|
const dynamicItemsPromise = this.generateDynamicItems();
|
||||||
const baseMenu = await this.injectDynamicItems(this.getStaticStructure(), dynamicItemsPromise);
|
const baseMenu = await this.injectDynamicItems(this.getStaticStructure(), dynamicItemsPromise);
|
||||||
const overrides = await this.loadOverrides();
|
const overrides = await this.loadOverrides();
|
||||||
return this.applyOverrides(baseMenu, overrides);
|
const freshMenu = this.applyOverrides(baseMenu, overrides);
|
||||||
|
|
||||||
|
this.menuCache = freshMenu;
|
||||||
|
this.lastModified = new Date();
|
||||||
|
this.cacheInitialized = true;
|
||||||
|
|
||||||
|
return { menu: freshMenu, fresh: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkForUpdates(ifModifiedSince: string): Promise<boolean> {
|
||||||
|
if (!this.cacheInitialized) {
|
||||||
|
await this.getFullMenuWithCache();
|
||||||
|
}
|
||||||
|
return !this.lastModified || new Date(ifModifiedSince) < this.lastModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyOverrides(menu: MenuItem, overrides: Partial<MenuItem>[]): MenuItem {
|
private applyOverrides(menu: MenuItem, overrides: Partial<MenuItem>[]): MenuItem {
|
||||||
|
|
@ -262,17 +283,26 @@ export class MenuService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMenuItem(id: string, update: Partial<MenuItem>): Promise<MenuItem> {
|
async updateMenuItem(id: string, update: Partial<MenuItem>): Promise<MenuItem> {
|
||||||
const fullMenu = await this.getFullMenu();
|
const { menu: fullMenu } = await this.getFullMenuWithCache();
|
||||||
const item = this.findMenuItem(fullMenu, id);
|
const item = this.findMenuItem(fullMenu, id);
|
||||||
|
|
||||||
if (!item) throw new Error('Menu item not found');
|
if (!item) throw new Error('Menu item not found');
|
||||||
Object.assign(item, update);
|
Object.assign(item, update);
|
||||||
|
|
||||||
|
|
||||||
|
this.menuCache = null;
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveOverrides(overrides: Partial<MenuItem>[]): Promise<void> {
|
async saveOverrides(overrides: Partial<MenuItem>[]): Promise<void> {
|
||||||
await fs.writeFile(this.menuOverridesPath, JSON.stringify({ overrides }, null, 2), 'utf-8');
|
await fs.writeFile(this.menuOverridesPath, JSON.stringify({ overrides }, null, 2), 'utf-8');
|
||||||
|
|
||||||
|
this.menuCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateCache(): void {
|
||||||
|
this.menuCache = null;
|
||||||
|
this.lastModified = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
private findMenuItem(menu: MenuItem, id: string): MenuItem | null {
|
private findMenuItem(menu: MenuItem, id: string): MenuItem | null {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Controller, Post, Get, Body, HttpException, HttpStatus } from '@nestjs/common';
|
||||||
|
import { RangeService } from './range.service';
|
||||||
|
import { MenuService } from './menu.service';
|
||||||
|
|
||||||
|
@Controller('ranges')
|
||||||
|
export class RangeController {
|
||||||
|
constructor(
|
||||||
|
private readonly rangeService: RangeService,
|
||||||
|
private readonly menuService: MenuService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
@Get('list')
|
||||||
|
async getRanges() {
|
||||||
|
try {
|
||||||
|
return await this.rangeService.getRanges();
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException('Failed to fetch ranges', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('update')
|
||||||
|
async updateRanges(
|
||||||
|
@Body() data: Array<{ name: string; ranges: { min: number; max: number; status: number }[] }>
|
||||||
|
) {
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
throw new HttpException('Invalid data format', HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.rangeService.updateRanges(data);
|
||||||
|
this.menuService.invalidateCache();
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,27 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { HttpService } from '@nestjs/axios';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RangeService {
|
export class RangeService {
|
||||||
constructor(private readonly httpService: HttpService) { }
|
private readonly rangesApiUrl: string;
|
||||||
|
private readonly rangesApiEndpoint: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly httpService: HttpService,
|
||||||
|
private readonly configService: ConfigService
|
||||||
|
) {
|
||||||
|
this.rangesApiUrl = this.configService.get<string>('RANGES_API_URL', 'localhost:3000');
|
||||||
|
this.rangesApiEndpoint = this.configService.get<string>('RANGES_API_ENDPOINT', 'localhost:3000');
|
||||||
|
}
|
||||||
|
|
||||||
async getRanges(): Promise<Record<string, Array<{ min: number; max: number; status: number }>>> {
|
async getRanges(): Promise<Record<string, Array<{ min: number; max: number; status: number }>>> {
|
||||||
try {
|
try {
|
||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.httpService.request({
|
this.httpService.request({
|
||||||
method: 'OPTIONS',
|
method: 'OPTIONS',
|
||||||
url: 'http://192.168.2.39:9999/api/ranges/9999',
|
url: `${this.rangesApiUrl}${this.rangesApiEndpoint}`,
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
@ -51,4 +61,18 @@ export class RangeService {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateRanges(data: Array<{ name: string; ranges: { min: number; max: number; status: number }[] }>) {
|
||||||
|
try {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.post(`${this.rangesApiUrl}${this.rangesApiEndpoint}`, data, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update ranges:', error);
|
||||||
|
throw new Error('Failed to update ranges');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@ export class PrometheusService {
|
||||||
async fetchMetricsRange(metric: string, start: number, end: number, step: number, filters: Record<string, string> = {}): Promise<PrometheusMetric[]> {
|
async fetchMetricsRange(metric: string, start: number, end: number, step: number, filters: Record<string, string> = {}): Promise<PrometheusMetric[]> {
|
||||||
const query = this.buildFilteredQuery(metric, {
|
const query = this.buildFilteredQuery(metric, {
|
||||||
...filters,
|
...filters,
|
||||||
instance: '192.168.2.34:9050' // Явно фильтруем по нужному instance
|
instance: '192.168.2.34:9050'
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const response = await lastValueFrom(
|
const response = await lastValueFrom(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue