new awesome build

This commit is contained in:
Антон
2026-01-28 22:37:47 +03:00
parent 848646003c
commit fcb8f6bac7
119 changed files with 7291 additions and 5575 deletions

View File

@@ -0,0 +1,73 @@
import axios from 'axios';
import { useAppConfig } from './useAppConfig';
// Singleton instances
const apiClient = axios.create();
const profilesApiClient = axios.create();
// Helper to get base URLs
const getBaseUrl = (config) => config?.api_base_url || 'http://localhost:5001/api/v1';
const getProfilesBaseUrl = (config) => config?.profiles_api_base_url || 'http://localhost:8000';
// Add interceptors to handle Auth and Dynamic Base URLs
const setupInterceptors = (instance, getBase) => {
instance.interceptors.request.use((reqConfig) => {
const { config } = useAppConfig();
reqConfig.baseURL = getBase(config.value);
const token = localStorage.getItem('ovpmon_token');
if (token) {
reqConfig.headers.Authorization = `Bearer ${token}`;
}
return reqConfig;
});
instance.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('ovpmon_token');
localStorage.removeItem('ovpmon_user');
// Redirecting using window.location for absolute refresh
if (window.location.pathname !== '/login') {
window.location.href = '/login';
}
}
return Promise.reject(error);
}
);
};
setupInterceptors(apiClient, getBaseUrl);
setupInterceptors(profilesApiClient, getProfilesBaseUrl);
export function useApi() {
const fetchStats = async () => {
const res = await apiClient.get('/stats');
return res.data;
};
const fetchClientHistory = async (clientId, range) => {
const res = await apiClient.get(`/stats/${clientId}`, { params: { range } });
return res.data;
};
const fetchAnalytics = async (range) => {
const res = await apiClient.get('/analytics', { params: { range } });
return res.data;
};
const fetchCertificates = async () => {
const res = await apiClient.get('/certificates');
return res.data;
};
return {
apiClient,
profilesApiClient,
fetchStats,
fetchClientHistory,
fetchAnalytics,
fetchCertificates
};
}

View File

@@ -0,0 +1,28 @@
import { ref } from 'vue';
const config = ref(null);
const isLoaded = ref(false);
export function useAppConfig() {
const loadConfig = async () => {
if (isLoaded.value) return;
try {
const response = await fetch('/config.json');
config.value = await response.json();
isLoaded.value = true;
} catch (error) {
console.error('Failed to load configuration:', error);
// Fallback or critical error handling
config.value = {
api_base_url: 'http://localhost:5001/api/v1',
refresh_interval: 30000
};
}
};
return {
config,
isLoaded,
loadConfig
};
}

View File

@@ -0,0 +1,43 @@
export function useFormatters() {
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 B';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
function formatRate(rate) {
return parseFloat(rate).toFixed(3) + ' Mbps';
}
function parseServerDate(dateStr) {
if (!dateStr) return null;
// Handle ISO strings with Z
if (dateStr.endsWith('Z')) return new Date(dateStr);
// Assume format YYYY-MM-DD HH:MM:SS (standard backend output)
const [datePart, timePart] = dateStr.split(' ');
if (!datePart || !timePart) {
// Fallback for other formats
let isoStr = dateStr.replace(' ', 'T');
if (!isoStr.endsWith('Z') && !isoStr.includes('+')) {
isoStr += 'Z';
}
return new Date(isoStr);
}
const [y, m, d] = datePart.split('-').map(Number);
const [h, min, s] = timePart.split(':').map(Number);
// Construct Date in UTC
return new Date(Date.UTC(y, m - 1, d, h, min, s !== undefined ? s : 0));
}
return {
formatBytes,
formatRate,
parseServerDate
};
}