Merge branch 'frontend' of gitea.d3m0k1d.ru:d3m0k1d/HellreigN into HEAD
ci-front / build (push) Successful in 1m54s

This commit is contained in:
nikita
2026-04-04 21:42:45 +03:00
3 changed files with 82 additions and 31 deletions
@@ -22,14 +22,14 @@ class AgentApiService {
async getAgents(): Promise<AgentInfo[]> { async getAgents(): Promise<AgentInfo[]> {
const response = await apiClient.get<AgentInfo[]>(this.basePath); const response = await apiClient.get<AgentInfo[]>(this.basePath);
return response.data; return Array.isArray(response.data) ? response.data : [];
} }
async getUsers(): Promise<TokenUser[]> { async getUsers(): Promise<TokenUser[]> {
const response = await apiClient.get<TokenUser[]>( const response = await apiClient.get<TokenUser[]>(
`${this.authBasePath}/tokens`, `${this.authBasePath}/tokens`,
); );
return response.data; return Array.isArray(response.data) ? response.data : [];
} }
async createUser(data: TokenCreate): Promise<void> { async createUser(data: TokenCreate): Promise<void> {
@@ -45,20 +45,27 @@ class AgentApiService {
} }
async searchLogs(filters?: LogFilters): Promise<LogEntry[]> { async searchLogs(filters?: LogFilters): Promise<LogEntry[]> {
const response = await apiClient.get<LogEntry[]>( const response = await apiClient.get<LogEntry[]>(this.logsBasePath, {
`${this.logsBasePath}/mock`,
{
params: { params: {
level: filters?.level, level: filters?.level || undefined,
service: filters?.service, service: filters?.service || undefined,
agent: filters?.agent, agent: filters?.agent || undefined,
date_from: filters?.date_from, date_from: filters?.date_from || undefined,
date_to: filters?.date_to, date_to: filters?.date_to || undefined,
limit: filters?.limit ?? 100, limit: filters?.limit ?? 100,
offset: filters?.offset ?? 0, offset: filters?.offset ?? 0,
}, },
}, });
if (!Array.isArray(response.data)) {
console.error(
"[Logs] Unexpected response format:",
typeof response.data,
response.data,
); );
return [];
}
return response.data; return response.data;
} }
@@ -74,21 +81,21 @@ class AgentApiService {
const response = await apiClient.get<string[]>( const response = await apiClient.get<string[]>(
`${this.logsBasePath}/agents`, `${this.logsBasePath}/agents`,
); );
return response.data; return Array.isArray(response.data) ? response.data : [];
} }
async getDistinctLevels(): Promise<string[]> { async getDistinctLevels(): Promise<string[]> {
const response = await apiClient.get<string[]>( const response = await apiClient.get<string[]>(
`${this.logsBasePath}/levels`, `${this.logsBasePath}/levels`,
); );
return response.data; return Array.isArray(response.data) ? response.data : [];
} }
async getDistinctServices(): Promise<string[]> { async getDistinctServices(): Promise<string[]> {
const response = await apiClient.get<string[]>( const response = await apiClient.get<string[]>(
`${this.logsBasePath}/services`, `${this.logsBasePath}/services`,
); );
return response.data; return Array.isArray(response.data) ? response.data : [];
} }
// User management methods // User management methods
@@ -96,6 +103,9 @@ class AgentApiService {
const response = await apiClient.get<TokenUser>( const response = await apiClient.get<TokenUser>(
`${this.authBasePath}/users/${login}`, `${this.authBasePath}/users/${login}`,
); );
if (!response.data || typeof response.data !== "object") {
throw new Error(`User not found: ${login}`);
}
return response.data; return response.data;
} }
@@ -103,7 +113,7 @@ class AgentApiService {
const response = await apiClient.get<TokenUser[]>( const response = await apiClient.get<TokenUser[]>(
`${this.authBasePath}/users/inactive`, `${this.authBasePath}/users/inactive`,
); );
return response.data; return Array.isArray(response.data) ? response.data : [];
} }
async updateUser(login: string, data: TokenUpdate): Promise<void> { async updateUser(login: string, data: TokenUpdate): Promise<void> {
+52 -11
View File
@@ -63,12 +63,20 @@ export const LogsPage: React.FC = () => {
try { try {
const filters = getFilters(); const filters = getFilters();
const data = await agentApiService.searchLogs(filters); const data = await agentApiService.searchLogs(filters);
if (!Array.isArray(data)) {
console.error("[Logs] Expected array, got:", typeof data);
setLogs([]);
setTotalLogs(0);
return;
}
setLogs(data); setLogs(data);
setTotalLogs(data.length); setTotalLogs(data.length);
} catch (err) { } catch (err) {
setError( setError(
err instanceof Error ? err.message : "Ошибка при загрузке логов", err instanceof Error ? err.message : "Ошибка при загрузке логов",
); );
setLogs([]);
setTotalLogs(0);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -76,14 +84,44 @@ export const LogsPage: React.FC = () => {
const fetchDistinctData = useCallback(async () => { const fetchDistinctData = useCallback(async () => {
try { try {
const [services, agents] = await Promise.all([ const [servicesResult, agentsResult] = await Promise.allSettled([
agentApiService.getDistinctServices(), agentApiService.getDistinctServices(),
agentApiService.getDistinctAgents(), agentApiService.getDistinctAgents(),
]); ]);
setAvailableServices(services);
setAvailableAgents(agents); if (
servicesResult.status === "fulfilled" &&
Array.isArray(servicesResult.value)
) {
setAvailableServices(servicesResult.value);
} else {
console.error(
"[Logs] Failed to fetch services:",
servicesResult.status === "rejected"
? servicesResult.reason
: "non-array response",
);
setAvailableServices([]);
}
if (
agentsResult.status === "fulfilled" &&
Array.isArray(agentsResult.value)
) {
setAvailableAgents(agentsResult.value);
} else {
console.error(
"[Logs] Failed to fetch agents:",
agentsResult.status === "rejected"
? agentsResult.reason
: "non-array response",
);
setAvailableAgents([]);
}
} catch (err) { } catch (err) {
console.error("Failed to fetch distinct data:", err); console.error("[Logs] Failed to fetch distinct data:", err);
setAvailableServices([]);
setAvailableAgents([]);
} }
}, []); }, []);
@@ -108,8 +146,10 @@ export const LogsPage: React.FC = () => {
setOffset(Math.max(0, offset - limit)); setOffset(Math.max(0, offset - limit));
}; };
const formatTimestamp = (timestamp: string) => { const formatTimestamp = (timestamp: string | undefined | null) => {
if (!timestamp) return "—";
const date = new Date(timestamp); const date = new Date(timestamp);
if (isNaN(date.getTime())) return "—";
return date.toLocaleString("ru-RU", { return date.toLocaleString("ru-RU", {
year: "numeric", year: "numeric",
month: "2-digit", month: "2-digit",
@@ -266,8 +306,9 @@ export const LogsPage: React.FC = () => {
</thead> </thead>
<tbody> <tbody>
{logs.map((log, index) => { {logs.map((log, index) => {
const level = log.level || "INFO";
const colors = const colors =
logLevelColors[log.level] || logLevelColors.INFO; logLevelColors[level] || logLevelColors.INFO;
return ( return (
<tr <tr
key={index} key={index}
@@ -295,27 +336,27 @@ export const LogsPage: React.FC = () => {
borderColor: colors.border, borderColor: colors.border,
}} }}
> >
{logLevelIcons[log.level]} {logLevelIcons[level]}
{log.level} {level}
</span> </span>
</td> </td>
<td <td
className="px-4 py-3 text-sm" className="px-4 py-3 text-sm"
style={{ color: "var(--text-primary)" }} style={{ color: "var(--text-primary)" }}
> >
{log.service} {log.service || "—"}
</td> </td>
<td <td
className="px-4 py-3 text-sm font-mono" className="px-4 py-3 text-sm font-mono"
style={{ color: "var(--text-primary)" }} style={{ color: "var(--text-primary)" }}
> >
{log.agent} {log.agent || "—"}
</td> </td>
<td <td
className="px-4 py-3 text-sm" className="px-4 py-3 text-sm"
style={{ color: "var(--text-primary)" }} style={{ color: "var(--text-primary)" }}
> >
{log.message} {log.message || "—"}
</td> </td>
</tr> </tr>
); );
+1 -1
View File
@@ -22,7 +22,7 @@ class ApiClient {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
validateStatus: (status) => { validateStatus: (status) => {
return status >= 200 && status < 500; return status >= 200 && status < 400;
}, },
}); });