AGDNS-4277 generate client api using openapi specs

This commit is contained in:
Ildar Kamalov 2026-07-17 16:22:40 +03:00
parent ef585d45cd
commit c79ff5454e
166 changed files with 4908 additions and 1062 deletions

View file

@ -67,4 +67,13 @@ module.exports = {
},
],
},
overrides: [
{
// Orval-generated files — max-len is controlled by prettier
files: ['src/api/generated.ts', 'src/api/model/**/*.ts'],
rules: {
'max-len': 'off',
},
},
],
};

View file

@ -2,24 +2,24 @@
## Table of Contents
- [AGENTS.md — AdGuard Home client\_v2 (SolidJS Frontend)](#agentsmd--adguard-home-client_v2-solidjs-frontend)
- [Table of Contents](#table-of-contents)
- [AGENTS.md — AdGuard Home client_v2 (SolidJS Frontend)](#agentsmd--adguard-home-client_v2-solidjs-frontend)
- [Table of Contents](#table-of-contents)
- [Project Overview](#project-overview)
- [Technical Context](#technical-context)
- [Project Structure](#project-structure)
- [Build And Test Commands](#build-and-test-commands)
- [Contribution Instructions](#contribution-instructions)
- [Code Guidelines](#code-guidelines)
- [System Design](#system-design)
- [Architecture](#architecture)
- [Code Quality](#code-quality)
- [Testing](#testing)
- [Dependency Management](#dependency-management)
- [Configuration \& Documentation](#configuration--documentation)
- [Markdown Formatting](#markdown-formatting)
- [Other](#other)
- [Accessibility](#accessibility)
- [Translations](#translations)
- [System Design](#system-design)
- [Architecture](#architecture)
- [Code Quality](#code-quality)
- [Testing](#testing)
- [Dependency Management](#dependency-management)
- [Configuration \& Documentation](#configuration--documentation)
- [Markdown Formatting](#markdown-formatting)
- [Other](#other)
- [Accessibility](#accessibility)
- [Translations](#translations)
# Project Overview
@ -77,7 +77,7 @@ client_v2/
│ ├── index.tsx # Main app entry (renders <App/>)
│ ├── index.pcss # Global CSS (vars, reset)
│ ├── initialState.ts # Domain model types + initial form state
│ ├── api/ # Single Api class — all HTTP calls
│ ├── api/ # Orval-generated typed client + customFetch mutator
│ ├── common/
│ │ ├── controls/ # Input primitives (Checkbox, Input, Select, Switch, …)
│ │ ├── ui/ # Higher-level UI (Button, Dialog, Table, Tabs, Sidebar, …)
@ -166,9 +166,10 @@ no server process of its own. Design for the browser environment:
- **Stateless client, stateful backend.** The UI holds no durable state; all
configuration and data persist in the AdGuard Home backend via the `/control`
HTTP API. Treat the browser as a thin view layer.
- **Single source of HTTP.** All backend calls go through the singleton
`apiClient` in `src/api/Api.ts`. Do not call `fetch` directly from components
or stores — add a typed method to the `Api` class instead.
- **Single source of HTTP.** All backend calls go through the orval-generated
typed client in `src/api/generated.ts` (with the `customFetch` mutator in
`src/api/customFetch.ts`). Do not call `fetch` directly from components
or stores — import a generated function instead.
- **No shared server memory.** Multiple browser tabs / reloads are independent;
never rely on in-memory module state surviving a reload. Re-fetch data in
component `onMount` rather than assuming a store is already populated.
@ -199,8 +200,9 @@ Universal design principles the codebase follows:
→ api → backend. Stores never import components; api never imports stores.
- **Explicit Boundaries** — modules interact through named exports; no
reaching into another module's internals.
- **Data Flow Clarity** — user action → store action function → `apiClient`
`setState` → reactive UI update. Data moves in one predictable path.
- **Data Flow Clarity** — user action → store action function → generated
API function → `setState` → reactive UI update. Data moves in one
predictable path.
- **Minimize Coupling, Maximize Cohesion** — stores are self-contained and
imported directly (no Context providers); components depend on narrow store
exports.
@ -221,7 +223,7 @@ This project's layers, from top to bottom:
| Components | Render UI, handle user interaction | `src/components/Dashboard/`, `src/components/Clients/` |
| Common UI | Reusable controls and primitives | `src/common/controls/Select/`, `src/common/ui/Button/` |
| Stores | Domain state + async actions | `src/stores/clients.ts`, `src/stores/queryLogs.ts` |
| API | HTTP transport to backend | `src/api/Api.ts` |
| API | HTTP transport to backend | `src/api/generated.ts`, `src/api/customFetch.ts` |
| Helpers | Pure utilities, validators | `src/helpers/`, `src/lib/` |
```text
@ -229,7 +231,7 @@ Components (pages, controls)
Stores (domain state + actions)
API (apiClient → fetch /control)
API (generated functions → customFetch → fetch /control)
AdGuard Home backend (Go)
```
@ -246,7 +248,7 @@ must not depend on stores or components. Helpers are pure and dependency-free.
## Code Quality
- **Path aliases**: Use the `panel/*` alias for all `src/` imports
(e.g. `import { apiClient } from 'panel/api/Api'`). Use `Twosky` for the
(e.g. `import { status } from 'panel/api/generated'`). Use `Twosky` for the
root `.twosky.json` config. Avoid deep relative paths (`../../..`).
- **Component conventions**: PascalCase directories and component files.
Each component lives in its own directory with an `index.tsx` and a
@ -284,12 +286,12 @@ must not depend on stores or components. Helpers are pure and dependency-free.
`createMemo` (e.g., table columns), **access the prop in the memo body**
so SolidJS tracks the dependency.
- **Stores**: Module-scoped `createStore` singletons exported directly — no
Context/Provider. Async actions set a `processing*` flag, call `apiClient`,
then `setState`. Errors are reported via `addErrorToast`.
Context/Provider. Async actions set a `processing*` flag, call generated
API functions, then `setState`. Errors are reported via `addErrorToast`.
- **Naming**: Files `PascalCase.tsx` for components, `camelCase.ts` for
stores/helpers. CSS module files `*.module.pcss`.
- **Error handling**: API errors throw from `Api.ts`; store actions catch and
surface a toast. Do not swallow errors silently.
- **Error handling**: API errors throw from `customFetch.ts`; store actions
catch and surface a toast. Do not swallow errors silently.
- **Logging**: `console.warn` and `console.error` are allowed; `console.log`
is disallowed by ESLint.
- **Static analysis gates**: ESLint, Prettier, and `tsc --noEmit` must pass.
@ -312,8 +314,8 @@ must not depend on stores or components. Helpers are pure and dependency-free.
`@testing-library/jest-dom` matchers.
- **Store/helper tests**: Call exported action/builder functions directly and
assert on returned values or state (e.g.
`src/__tests__/clientForm/buildClientConfig.test.ts`). Mock `apiClient` when
a test would otherwise hit the network.
`src/__tests__/clientForm/buildClientConfig.test.ts`). Mock generated API
functions when a test would otherwise hit the network.
- **Naming**: Mirror the source path under `__tests__/`
(e.g. `stores/clients.ts``__tests__/stores/clients.test.ts`).
- **E2E tests**: Playwright, configured in `playwright.config.ts`

27
client_v2/orval.config.ts Normal file
View file

@ -0,0 +1,27 @@
import { defineConfig } from 'orval';
export default defineConfig({
adguardHome: {
input: {
target: '../openapi/openapi.yaml',
},
output: {
target: './src/api/generated.ts',
schemas: './src/api/model',
client: 'fetch',
baseUrl: 'control',
prettier: true,
override: {
header: false,
mutator: {
path: './src/api/customFetch.ts',
name: 'customFetch',
},
fetch: {
includeHttpResponseReturnType: false,
},
enumGenerationType: 'union',
},
},
},
});

File diff suppressed because it is too large Load diff

View file

@ -19,7 +19,8 @@
"test:e2e:codegen": "npx playwright codegen",
"typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch",
"translations:check": "node ./scripts/check-translations.js"
"translations:check": "node ./scripts/check-translations.js",
"api:generate": "orval --config ./orval.config.ts && bash scripts/postgenerate.sh"
},
"type": "module",
"dependencies": {
@ -69,6 +70,7 @@
"html-webpack-plugin": "^5.6.0",
"jsdom": "^26.1.0",
"mini-css-extract-plugin": "^2.9.0",
"orval": "^8.22.0",
"playwright": "^1.61.0",
"postcss": "^8.5.6",
"postcss-import": "^16.1.1",

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
npx prettier --write "src/api/generated.ts" "src/api/model/**/*.ts"
npx eslint --quiet --fix "src/api/generated.ts" "src/api/model/**/*.ts" || true

View file

@ -1,17 +1,15 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mocks = vi.hoisted(() => ({
getAccessList: vi.fn(),
setAccessList: vi.fn(),
accessList: vi.fn(),
accessSet: vi.fn(),
addSuccessToast: vi.fn(),
addErrorToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
getAccessList: mocks.getAccessList,
setAccessList: mocks.setAccessList,
},
vi.mock('panel/api/generated', () => ({
accessList: mocks.accessList,
accessSet: mocks.accessSet,
}));
vi.mock('panel/stores/toasts', () => ({
addSuccessToast: mocks.addSuccessToast,
@ -24,13 +22,13 @@ describe('toggleClientBlock', () => {
beforeEach(() => vi.clearAllMocks());
it('not-disallowed + allowlist mode with >1 allowed → removes from allowed', async () => {
mocks.getAccessList.mockResolvedValue({
mocks.accessList.mockResolvedValue({
allowed_clients: ['1.1.1.1', '2.2.2.2'],
disallowed_clients: [],
blocked_hosts: [],
});
await toggleClientBlock('1.1.1.1', false, '');
expect(mocks.setAccessList).toHaveBeenCalledWith({
expect(mocks.accessSet).toHaveBeenCalledWith({
allowed_clients: ['2.2.2.2'],
disallowed_clients: [],
blocked_hosts: [],
@ -38,13 +36,13 @@ describe('toggleClientBlock', () => {
});
it('not-disallowed, no allowlist → adds to disallowed', async () => {
mocks.getAccessList.mockResolvedValue({
mocks.accessList.mockResolvedValue({
allowed_clients: [],
disallowed_clients: [],
blocked_hosts: [],
});
await toggleClientBlock('3.3.3.3', false, '');
expect(mocks.setAccessList).toHaveBeenCalledWith({
expect(mocks.accessSet).toHaveBeenCalledWith({
allowed_clients: [],
disallowed_clients: ['3.3.3.3'],
blocked_hosts: [],
@ -52,13 +50,13 @@ describe('toggleClientBlock', () => {
});
it('disallowed + allowlist mode → adds to allowed', async () => {
mocks.getAccessList.mockResolvedValue({
mocks.accessList.mockResolvedValue({
allowed_clients: ['1.1.1.1'],
disallowed_clients: ['2.2.2.2'],
blocked_hosts: [],
});
await toggleClientBlock('2.2.2.2', true, '');
expect(mocks.setAccessList).toHaveBeenCalledWith({
expect(mocks.accessSet).toHaveBeenCalledWith({
allowed_clients: ['1.1.1.1', '2.2.2.2'],
disallowed_clients: ['2.2.2.2'],
blocked_hosts: [],
@ -66,13 +64,13 @@ describe('toggleClientBlock', () => {
});
it('disallowed, no allowlist → removes from disallowed (uses rule)', async () => {
mocks.getAccessList.mockResolvedValue({
mocks.accessList.mockResolvedValue({
allowed_clients: [],
disallowed_clients: ['client:X'],
blocked_hosts: [],
});
await toggleClientBlock('1.2.3.4', true, 'client:X');
expect(mocks.setAccessList).toHaveBeenCalledWith({
expect(mocks.accessSet).toHaveBeenCalledWith({
allowed_clients: [],
disallowed_clients: [],
blocked_hosts: [],

View file

@ -1,22 +1,20 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mocks = vi.hoisted(() => ({
getTlsStatus: vi.fn(),
getGlobalVersion: vi.fn(),
tlsStatus: vi.fn(),
getVersionJson: vi.fn(),
getProfile: vi.fn(),
addErrorToast: vi.fn(),
addSuccessToast: vi.fn(),
addNoticeToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
vi.mock('panel/api/generated', () => ({
baseUrl: 'http://x',
getGlobalVersion: mocks.getGlobalVersion,
getVersionJson: mocks.getVersionJson,
getProfile: mocks.getProfile,
getUpdate: vi.fn(),
getTlsStatus: mocks.getTlsStatus,
},
beginUpdate: vi.fn(),
tlsStatus: mocks.tlsStatus,
}));
vi.mock('panel/stores/toasts', () => ({
addErrorToast: mocks.addErrorToast,
@ -30,12 +28,12 @@ describe('getDnsStatus', () => {
beforeEach(() => vi.clearAllMocks());
it('fetches TLS status when the core is running (FR-005)', async () => {
mocks.getGlobalVersion.mockResolvedValue({
mocks.getVersionJson.mockResolvedValue({
disabled: true,
new_version: 'x',
});
mocks.getProfile.mockResolvedValue({ name: 'n', theme: 't' });
mocks.getTlsStatus.mockResolvedValue({ enabled: false });
mocks.tlsStatus.mockResolvedValue({ enabled: false });
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(
@ -54,6 +52,6 @@ describe('getDnsStatus', () => {
await getDnsStatus();
// allow microtasks for chained getVersion/getTlsStatus/getProfile
await new Promise((r) => setTimeout(r, 0));
expect(mocks.getTlsStatus).toHaveBeenCalled();
expect(mocks.tlsStatus).toHaveBeenCalled();
});
});

View file

@ -1,22 +1,20 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mocks = vi.hoisted(() => ({
findActiveDhcp: vi.fn(),
getDhcpInterfaces: vi.fn(),
getDhcpStatus: vi.fn(),
setDhcpConfig: vi.fn(),
checkActiveDhcp: vi.fn(),
dhcpInterfaces: vi.fn(),
dhcpStatus: vi.fn(),
dhcpSetConfig: vi.fn(),
addErrorToast: vi.fn(),
addSuccessToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
findActiveDhcp: mocks.findActiveDhcp,
getDhcpInterfaces: mocks.getDhcpInterfaces,
getDhcpStatus: mocks.getDhcpStatus,
getGlobalStatus: vi.fn(),
setDhcpConfig: mocks.setDhcpConfig,
},
vi.mock('panel/api/generated', () => ({
checkActiveDhcp: mocks.checkActiveDhcp,
dhcpInterfaces: mocks.dhcpInterfaces,
dhcpStatus: mocks.dhcpStatus,
status: vi.fn(),
dhcpSetConfig: mocks.dhcpSetConfig,
}));
vi.mock('panel/stores/toasts', () => ({
addErrorToast: mocks.addErrorToast,
@ -29,19 +27,19 @@ describe('findActiveDhcp', () => {
beforeEach(() => vi.clearAllMocks());
it('passes { interface } not a bare string', async () => {
mocks.findActiveDhcp.mockResolvedValue({
mocks.checkActiveDhcp.mockResolvedValue({
v4: { other_server: { found: 'yes' }, static_ip: { static: 'yes' } },
v6: { other_server: {} },
});
await findActiveDhcp('eth0');
expect(mocks.findActiveDhcp).toHaveBeenCalledWith({ interface: 'eth0' });
expect(mocks.checkActiveDhcp).toHaveBeenCalledWith({ interface: 'eth0' });
});
it('shows dhcp_found error with retry action when another DHCP server detected', async () => {
mocks.getDhcpInterfaces.mockResolvedValue({
mocks.dhcpInterfaces.mockResolvedValue({
eth0: { ipv4_addresses: ['1.1.1.1'], ipv6_addresses: [] },
});
mocks.findActiveDhcp.mockResolvedValue({
mocks.checkActiveDhcp.mockResolvedValue({
v4: {
other_server: { found: 'yes' },
static_ip: { static: 'yes', ip: 'x' },
@ -58,10 +56,10 @@ describe('findActiveDhcp', () => {
});
it('shows dhcp_not_found success toast when clean', async () => {
mocks.getDhcpInterfaces.mockResolvedValue({
mocks.dhcpInterfaces.mockResolvedValue({
eth0: { ipv4_addresses: ['1.1.1.1'], ipv6_addresses: [] },
});
mocks.findActiveDhcp.mockResolvedValue({
mocks.checkActiveDhcp.mockResolvedValue({
v4: {
other_server: { found: 'no' },
static_ip: { static: 'yes', ip: '1.1.1.1' },
@ -77,7 +75,7 @@ describe('findActiveDhcp', () => {
describe('setDhcpConfig', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.setDhcpConfig.mockResolvedValue(undefined);
mocks.dhcpSetConfig.mockResolvedValue(undefined);
});
it('shows dhcp_config_saved toast (FR-010)', async () => {
@ -92,12 +90,12 @@ describe('setDhcpConfig', () => {
describe('toggleDhcp', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.setDhcpConfig.mockResolvedValue(undefined);
mocks.dhcpSetConfig.mockResolvedValue(undefined);
});
it('computes enabled from passed config, not current state (FR-011)', async () => {
await toggleDhcp({ enabled: false, interface_name: 'eth0' });
expect(mocks.setDhcpConfig).toHaveBeenCalledWith(
expect(mocks.dhcpSetConfig).toHaveBeenCalledWith(
expect.objectContaining({ enabled: true }),
);
});

View file

@ -1,17 +1,15 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mocks = vi.hoisted(() => ({
getDnsConfig: vi.fn(),
setDnsConfig: vi.fn(),
dnsInfo: vi.fn(),
dnsConfig: vi.fn(),
addErrorToast: vi.fn(),
addSuccessToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
getDnsConfig: mocks.getDnsConfig,
setDnsConfig: mocks.setDnsConfig,
},
vi.mock('panel/api/generated', () => ({
dnsInfo: mocks.dnsInfo,
dnsConfig: mocks.dnsConfig,
}));
vi.mock('panel/stores/toasts', () => ({
addErrorToast: mocks.addErrorToast,
@ -27,7 +25,7 @@ describe('getDnsConfig', () => {
beforeEach(() => vi.clearAllMocks());
it('defaults null blocking IPs and empty upstream_mode (FR-012)', async () => {
mocks.getDnsConfig.mockResolvedValue({
mocks.dnsInfo.mockResolvedValue({
blocking_ipv4: null,
blocking_ipv6: null,
upstream_mode: '',
@ -43,7 +41,7 @@ describe('toggleResolveClients', () => {
beforeEach(() => vi.clearAllMocks());
it('toggles resolve_clients and persists (FR-032)', async () => {
mocks.setDnsConfig.mockResolvedValue({});
mocks.dnsConfig.mockResolvedValue({});
const before = dnsConfigState.resolve_clients;
await toggleResolveClients();
@ -54,12 +52,12 @@ describe('toggleResolveClients', () => {
expect(dnsConfigState.resolve_clients).toBe(before);
});
it('calls apiClient.setDnsConfig with inverted value (FR-032)', async () => {
mocks.setDnsConfig.mockResolvedValue({});
it('calls dnsConfig with inverted value (FR-032)', async () => {
mocks.dnsConfig.mockResolvedValue({});
const before = dnsConfigState.resolve_clients;
await toggleResolveClients();
expect(mocks.setDnsConfig).toHaveBeenCalledWith({
expect(mocks.dnsConfig).toHaveBeenCalledWith({
resolve_clients: !before,
});
});

View file

@ -1,18 +1,16 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mocks = vi.hoisted(() => ({
setTlsConfig: vi.fn(),
validateTlsConfig: vi.fn(),
tlsConfigure: vi.fn(),
tlsValidate: vi.fn(),
addErrorToast: vi.fn(),
addSuccessToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
setTlsConfig: mocks.setTlsConfig,
validateTlsConfig: mocks.validateTlsConfig,
getGlobalStatus: vi.fn(),
},
vi.mock('panel/api/generated', () => ({
tlsConfigure: mocks.tlsConfigure,
tlsValidate: mocks.tlsValidate,
status: vi.fn(),
}));
vi.mock('panel/stores/toasts', () => ({
addErrorToast: mocks.addErrorToast,
@ -31,7 +29,7 @@ describe('setTlsConfig', () => {
beforeEach(() => vi.clearAllMocks());
it('defaults empty ports to 0 (FR-013)', async () => {
mocks.setTlsConfig.mockImplementation(async (v: any) => ({
mocks.tlsConfigure.mockImplementation(async (v: any) => ({
...v,
certificate_chain: '',
private_key: '',
@ -43,14 +41,14 @@ describe('setTlsConfig', () => {
port_dns_over_tls: '',
port_dns_over_quic: '',
});
const sent = mocks.setTlsConfig.mock.calls[0][0];
const sent = mocks.tlsConfigure.mock.calls[0][0];
expect(sent.port_https).toBe(0);
expect(sent.port_dns_over_tls).toBe(0);
expect(sent.port_dns_over_quic).toBe(0);
});
it('clears validation status fields when resetValidationStatus is called', async () => {
mocks.validateTlsConfig.mockResolvedValue({
mocks.tlsValidate.mockResolvedValue({
valid_chain: true,
valid_cert: true,
valid_key: true,
@ -94,7 +92,7 @@ describe('setTlsConfig', () => {
value: { protocol: 'http:', reload: reloadFn },
writable: true,
});
mocks.setTlsConfig.mockImplementation(async (v: any) => ({
mocks.tlsConfigure.mockImplementation(async (v: any) => ({
...v,
certificate_chain: '',
private_key: '',

View file

@ -15,12 +15,10 @@ const mocks = vi.hoisted(() => ({
addErrorToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
setRules: mocks.apiSetRules,
addFilter: mocks.apiAddFilter,
getFilteringStatus: mocks.apiGetFilteringStatus,
},
vi.mock('panel/api/generated', () => ({
filteringSetRules: mocks.apiSetRules,
filteringAddURL: mocks.apiAddFilter,
filteringStatus: mocks.apiGetFilteringStatus,
}));
vi.mock('panel/stores/toasts', () => ({

View file

@ -15,12 +15,10 @@ const mocks = vi.hoisted(() => ({
addErrorToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
setRules: mocks.apiSetRules,
addFilter: mocks.apiAddFilter,
getFilteringStatus: mocks.apiGetFilteringStatus,
},
vi.mock('panel/api/generated', () => ({
filteringSetRules: mocks.apiSetRules,
filteringAddURL: mocks.apiAddFilter,
filteringStatus: mocks.apiGetFilteringStatus,
}));
vi.mock('panel/stores/toasts', () => ({

View file

@ -2,12 +2,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock the API client.
const mockCheckConfig = vi.fn();
vi.mock('panel/api/Api', () => ({
apiClient: {
checkConfig: (...args: unknown[]) => mockCheckConfig(...args),
getDefaultAddresses: vi.fn(),
setAllSettings: vi.fn(),
},
vi.mock('panel/api/generated', () => ({
installCheckConfig: (...args: unknown[]) => mockCheckConfig(...args),
installGetAddresses: vi.fn(),
installConfigure: vi.fn(),
}));
vi.mock('panel/stores/toasts', () => ({ addErrorToast: vi.fn() }));

View file

@ -2,10 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock the API client before importing the store.
const mockLogin = vi.fn().mockResolvedValue(undefined);
vi.mock('panel/api/Api', () => ({
apiClient: {
vi.mock('panel/api/generated', () => ({
login: (...args: unknown[]) => mockLogin(...args),
},
}));
// Mock addErrorToast so it does not depend on other stores.

View file

@ -1,11 +1,9 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { getAdditionalLogs, setFilteredLogs, queryLogsState } from 'panel/stores/queryLogs';
import { apiClient } from 'panel/api/Api';
import { queryLog } from 'panel/api/generated';
vi.mock('panel/api/Api', () => ({
apiClient: {
getQueryLog: vi.fn(),
},
vi.mock('panel/api/generated', () => ({
queryLog: vi.fn(),
}));
vi.mock('panel/stores/toasts', () => ({
@ -19,14 +17,14 @@ describe('queryLogs store', () => {
});
it('getAdditionalLogs appends filtered rows using the oldest cursor and filter', async () => {
(apiClient.getQueryLog as any).mockReset();
(queryLog as any).mockReset();
// Seed state: a full first page (20 rewritten) with more behind it.
const fullPage = Array.from({ length: 20 }, () => ({
reason: 'Rewrite',
question: {},
}));
(apiClient.getQueryLog as any).mockResolvedValueOnce({
(queryLog as any).mockResolvedValueOnce({
data: fullPage,
oldest: 'cur',
});
@ -35,7 +33,7 @@ describe('queryLogs store', () => {
expect(queryLogsState.logs).toHaveLength(20);
// Load more: one extra rewritten entry, then end of log.
(apiClient.getQueryLog as any).mockResolvedValueOnce({
(queryLog as any).mockResolvedValueOnce({
data: [{ reason: 'Rewrite', question: {} }],
oldest: '',
});
@ -43,7 +41,7 @@ describe('queryLogs store', () => {
await getAdditionalLogs();
// The load-more request must carry the cursor and the reason filter.
expect(apiClient.getQueryLog).toHaveBeenLastCalledWith(
expect(queryLog).toHaveBeenLastCalledWith(
expect.objectContaining({
older_than: 'cur',
reason: expect.arrayContaining([
@ -55,7 +53,7 @@ describe('queryLogs store', () => {
}),
);
// Must NOT send the deprecated response_status
const lastCall = (apiClient.getQueryLog as any).mock.calls.at(-1)[0];
const lastCall = (queryLog as any).mock.calls.at(-1)[0];
expect(lastCall).not.toHaveProperty('response_status');
expect(queryLogsState.logs).toHaveLength(21);
@ -64,16 +62,16 @@ describe('queryLogs store', () => {
});
it('setFilteredLogs sends reason strings for blocked status', async () => {
(apiClient.getQueryLog as any).mockReset();
(apiClient.getQueryLog as any).mockResolvedValue({
(queryLog as any).mockReset();
(queryLog as any).mockResolvedValue({
data: [{ reason: 'FilteredBlackList', question: {} }],
oldest: '',
});
await setFilteredLogs({ search: '', status: 'blocked', reason: 'all' });
const lastCall = (apiClient.getQueryLog as any).mock.calls.at(-1)[0];
expect(apiClient.getQueryLog).toHaveBeenLastCalledWith(
const lastCall = (queryLog as any).mock.calls.at(-1)[0];
expect(queryLog).toHaveBeenLastCalledWith(
expect.objectContaining({
reason: expect.arrayContaining([
'FilteredBlackList',
@ -87,7 +85,7 @@ describe('queryLogs store', () => {
});
it('does not mark the log as complete when additional loading stops', async () => {
(apiClient.getQueryLog as any).mockResolvedValue({
(queryLog as any).mockResolvedValue({
data: [],
oldest: 'next-cursor',
});
@ -99,7 +97,7 @@ describe('queryLogs store', () => {
});
it('accumulates pages until oldest is empty (short-polling)', async () => {
(apiClient.getQueryLog as any)
(queryLog as any)
.mockResolvedValueOnce({
data: [{ reason: 'Rewrite' }],
oldest: 'cur',
@ -112,7 +110,7 @@ describe('queryLogs store', () => {
});
await setFilteredLogs({ search: '', status: 'rewritten', reason: 'all' });
expect(apiClient.getQueryLog).toHaveBeenCalledTimes(2);
expect(queryLog).toHaveBeenCalledTimes(2);
expect(queryLogsState.processingGetLogs).toBe(false);
});
});

View file

@ -1,16 +1,14 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mocks = vi.hoisted(() => ({
getStats: vi.fn(),
searchClients: vi.fn(),
stats: vi.fn(),
clientsSearch: vi.fn(),
addErrorToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
getStats: mocks.getStats,
searchClients: mocks.searchClients,
},
vi.mock('panel/api/generated', () => ({
stats: mocks.stats,
clientsSearch: mocks.clientsSearch,
}));
vi.mock('panel/stores/toasts', () => ({
addErrorToast: mocks.addErrorToast,
@ -21,11 +19,11 @@ import { getStats, statsState } from 'panel/stores/stats';
describe('getStats', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.searchClients.mockResolvedValue([]);
mocks.clientsSearch.mockResolvedValue([]);
});
it('enriches top clients and stores normalizedTopClients (FR-001/019)', async () => {
mocks.getStats.mockResolvedValue({
mocks.stats.mockResolvedValue({
top_clients: [{ '1.2.3.4': 5 }],
avg_processing_time: 0.012,
top_blocked_domains: [],
@ -37,7 +35,7 @@ describe('getStats', () => {
await getStats();
// searchClients was called with discovered client ids
expect(mocks.searchClients).toHaveBeenCalledWith({
expect(mocks.clientsSearch).toHaveBeenCalledWith({
clients: [{ id: '1.2.3.4' }],
});
// normalizedTopClients is populated (configured bucket carries info name)
@ -45,11 +43,11 @@ describe('getStats', () => {
});
it('converts avg_processing_time to milliseconds, falsy passthrough (FR-002)', async () => {
mocks.getStats.mockResolvedValue({ avg_processing_time: 0.012 });
mocks.stats.mockResolvedValue({ avg_processing_time: 0.012 });
await getStats();
expect(statsState.avgProcessingTime).toBe(12);
mocks.getStats.mockResolvedValue({ avg_processing_time: 0 });
mocks.stats.mockResolvedValue({ avg_processing_time: 0 });
await getStats();
expect(statsState.avgProcessingTime).toBe(0); // not NaN
});

View file

@ -22,11 +22,9 @@ const mocks = vi.hoisted(() => ({
addErrorToast: vi.fn(),
}));
vi.mock('panel/api/Api', () => ({
apiClient: {
setRules: mocks.apiSetRules,
getFilteringStatus: mocks.apiGetFilteringStatus,
},
vi.mock('panel/api/generated', () => ({
filteringSetRules: mocks.apiSetRules,
filteringStatus: mocks.apiGetFilteringStatus,
}));
vi.mock('panel/stores/toasts', () => ({

View file

@ -1,746 +0,0 @@
import type { LocalesType } from 'panel/common/intl';
const BASE_URL = 'control';
import { getPathWithQueryString } from '../helpers/helpers';
import { QUERY_LOGS_PAGE_LIMIT, HTML_PAGES, R_PATH_LAST_PART, THEMES } from '../helpers/constants';
import intl from '../common/intl';
type Theme = (typeof THEMES)[keyof typeof THEMES];
class Api {
baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async makeRequest(path: any, method = 'POST', config: any = {}) {
const pathWithBase = `${this.baseUrl}/${path}`;
const url = config.params
? getPathWithQueryString(pathWithBase, config.params)
: pathWithBase;
const fetchConfig: RequestInit = { method };
const headers: Record<string, string> = {};
if (method !== 'GET' && config.data) {
headers['Content-Type'] = config.headers?.['Content-Type'] || 'application/json';
fetchConfig.body =
typeof config.data === 'string' ? config.data : JSON.stringify(config.data);
}
fetchConfig.headers = headers;
try {
const response = await fetch(url, fetchConfig);
const text = await response.text();
const data = text
? (() => {
try {
return JSON.parse(text);
} catch {
return text;
}
})()
: '';
if (!response.ok) {
const errorPath = url;
const { pathname } = document.location;
const shouldRedirect =
pathname !== HTML_PAGES.LOGIN && pathname !== HTML_PAGES.INSTALL;
if (response.status === 403 && shouldRedirect) {
const loginPageUrl = window.location.href.replace(
R_PATH_LAST_PART,
HTML_PAGES.LOGIN,
);
window.location.replace(loginPageUrl);
return false;
}
throw new Error(`${errorPath} | ${data} | ${response.status}`);
}
return data;
} catch (error) {
const errorPath = url;
if (error instanceof Error && error.message.includes('|')) {
throw error;
}
throw new Error(`${errorPath} | ${error.message || error}`);
}
}
// Global methods
GLOBAL_STATUS = { path: 'status', method: 'GET' };
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
getGlobalStatus() {
const { path, method } = this.GLOBAL_STATUS;
return this.makeRequest(path, method);
}
testUpstream(servers: any) {
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
const config = {
data: servers,
};
return this.makeRequest(path, method, config);
}
getGlobalVersion(data: any) {
const { path, method } = this.GLOBAL_VERSION;
const config = {
data,
};
return this.makeRequest(path, method, config);
}
getUpdate() {
const { path, method } = this.GLOBAL_UPDATE;
return this.makeRequest(path, method);
}
// Filtering
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
FILTERING_CONFIG = { path: 'filtering/config', method: 'POST' };
FILTERING_CHECK_HOST = { path: 'filtering/check_host', method: 'GET' };
getFilteringStatus() {
const { path, method } = this.FILTERING_STATUS;
return this.makeRequest(path, method);
}
refreshFilters(config: any) {
const { path, method } = this.FILTERING_REFRESH;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
addFilter(config: any) {
const { path, method } = this.FILTERING_ADD_FILTER;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
removeFilter(config: any) {
const { path, method } = this.FILTERING_REMOVE_FILTER;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
setRules(rules: any) {
const { path, method } = this.FILTERING_SET_RULES;
const parameters = {
data: rules,
};
return this.makeRequest(path, method, parameters);
}
setFiltersConfig(config: any) {
const { path, method } = this.FILTERING_CONFIG;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
setFilterUrl(config: any) {
const { path, method } = this.FILTERING_SET_URL;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
checkHost(params: any) {
const { path, method } = this.FILTERING_CHECK_HOST;
const url = getPathWithQueryString(path, params);
return this.makeRequest(url, method);
}
// Parental
PARENTAL_STATUS = { path: 'parental/status', method: 'GET' };
PARENTAL_ENABLE = { path: 'parental/enable', method: 'POST' };
PARENTAL_DISABLE = { path: 'parental/disable', method: 'POST' };
getParentalStatus() {
const { path, method } = this.PARENTAL_STATUS;
return this.makeRequest(path, method);
}
enableParentalControl() {
const { path, method } = this.PARENTAL_ENABLE;
return this.makeRequest(path, method);
}
disableParentalControl() {
const { path, method } = this.PARENTAL_DISABLE;
return this.makeRequest(path, method);
}
// Safebrowsing
SAFEBROWSING_STATUS = { path: 'safebrowsing/status', method: 'GET' };
SAFEBROWSING_ENABLE = { path: 'safebrowsing/enable', method: 'POST' };
SAFEBROWSING_DISABLE = { path: 'safebrowsing/disable', method: 'POST' };
getSafebrowsingStatus() {
const { path, method } = this.SAFEBROWSING_STATUS;
return this.makeRequest(path, method);
}
enableSafebrowsing() {
const { path, method } = this.SAFEBROWSING_ENABLE;
return this.makeRequest(path, method);
}
disableSafebrowsing() {
const { path, method } = this.SAFEBROWSING_DISABLE;
return this.makeRequest(path, method);
}
// Safesearch
SAFESEARCH_STATUS = { path: 'safesearch/status', method: 'GET' };
SAFESEARCH_UPDATE = { path: 'safesearch/settings', method: 'PUT' };
getSafesearchStatus() {
const { path, method } = this.SAFESEARCH_STATUS;
return this.makeRequest(path, method);
}
/**
* interface SafeSearchConfig {
"enabled": boolean,
"bing": boolean,
"duckduckgo": boolean,
"google": boolean,
"pixabay": boolean,
"yandex": boolean,
"youtube": boolean
* }
* @param {*} data - SafeSearchConfig
* @returns 200 ok
*/
updateSafesearch(data: any) {
const { path, method } = this.SAFESEARCH_UPDATE;
return this.makeRequest(path, method, { data });
}
// enableSafesearch() {
// const { path, method } = this.SAFESEARCH_ENABLE;
// return this.makeRequest(path, method);
// }
// disableSafesearch() {
// const { path, method } = this.SAFESEARCH_DISABLE;
// return this.makeRequest(path, method);
// }
// Language
async changeLanguage(config: { language: LocalesType }) {
const profile = await this.getProfile();
profile.language = config.language;
return this.setProfile(profile);
}
// Theme
async changeTheme(config: { theme: Theme }) {
const profile = await this.getProfile();
profile.theme = config.theme;
return this.setProfile(profile);
}
// DHCP
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
DHCP_UPDATE_STATIC_LEASE = { path: 'dhcp/update_static_lease', method: 'POST' };
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
DHCP_LEASES_RESET = { path: 'dhcp/reset_leases', method: 'POST' };
getDhcpStatus() {
const { path, method } = this.DHCP_STATUS;
return this.makeRequest(path, method);
}
getDhcpInterfaces() {
const { path, method } = this.DHCP_INTERFACES;
return this.makeRequest(path, method);
}
setDhcpConfig(config: any) {
const { path, method } = this.DHCP_SET_CONFIG;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
findActiveDhcp(req: any) {
const { path, method } = this.DHCP_FIND_ACTIVE;
const parameters = {
data: req,
};
return this.makeRequest(path, method, parameters);
}
addStaticLease(config: any) {
const { path, method } = this.DHCP_ADD_STATIC_LEASE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
removeStaticLease(config: any) {
const { path, method } = this.DHCP_REMOVE_STATIC_LEASE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
updateStaticLease(config: any) {
const { path, method } = this.DHCP_UPDATE_STATIC_LEASE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
resetDhcp() {
const { path, method } = this.DHCP_RESET;
return this.makeRequest(path, method);
}
resetDhcpLeases() {
const { path, method } = this.DHCP_LEASES_RESET;
return this.makeRequest(path, method);
}
// Installation
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' };
getDefaultAddresses() {
const { path, method } = this.INSTALL_GET_ADDRESSES;
return this.makeRequest(path, method);
}
setAllSettings(config: any) {
const { path, method } = this.INSTALL_CONFIGURE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
checkConfig(config: any) {
const { path, method } = this.INSTALL_CHECK_CONFIG;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
// DNS-over-HTTPS and DNS-over-TLS
TLS_STATUS = { path: 'tls/status', method: 'GET' };
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
getTlsStatus() {
const { path, method } = this.TLS_STATUS;
return this.makeRequest(path, method);
}
setTlsConfig(config: any) {
const { path, method } = this.TLS_CONFIG;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
validateTlsConfig(config: any) {
const { path, method } = this.TLS_VALIDATE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
// Per-client settings
GET_CLIENTS = { path: 'clients', method: 'GET' };
SEARCH_CLIENTS = { path: 'clients/search', method: 'POST' };
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' };
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' };
getClients() {
const { path, method } = this.GET_CLIENTS;
return this.makeRequest(path, method);
}
addClient(config: any) {
const { path, method } = this.ADD_CLIENT;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
deleteClient(config: any) {
const { path, method } = this.DELETE_CLIENT;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
updateClient(config: any) {
const { path, method } = this.UPDATE_CLIENT;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
searchClients(config: any) {
const { path, method } = this.SEARCH_CLIENTS;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
// DNS access settings
ACCESS_LIST = { path: 'access/list', method: 'GET' };
ACCESS_SET = { path: 'access/set', method: 'POST' };
getAccessList() {
const { path, method } = this.ACCESS_LIST;
return this.makeRequest(path, method);
}
setAccessList(config: any) {
const { path, method } = this.ACCESS_SET;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
// DNS rewrites
REWRITES_LIST = { path: 'rewrite/list', method: 'GET' };
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
REWRITE_UPDATE = { path: 'rewrite/update', method: 'PUT' };
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
getRewritesList() {
const { path, method } = this.REWRITES_LIST;
return this.makeRequest(path, method);
}
addRewrite(config: any) {
const { path, method } = this.REWRITE_ADD;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
updateRewrite(config: any) {
const { path, method } = this.REWRITE_UPDATE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
deleteRewrite(config: any) {
const { path, method } = this.REWRITE_DELETE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
REWRITE_SETTINGS = { path: 'rewrite/settings', method: 'GET' };
REWRITE_SETTINGS_UPDATE = { path: 'rewrite/settings/update', method: 'PUT' };
getRewriteSettings() {
const { path, method } = this.REWRITE_SETTINGS;
return this.makeRequest(path, method);
}
updateRewriteSettings(config: { enabled: boolean }) {
const { path, method } = this.REWRITE_SETTINGS_UPDATE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
// Blocked services
BLOCKED_SERVICES_GET = { path: 'blocked_services/get', method: 'GET' };
BLOCKED_SERVICES_UPDATE = { path: 'blocked_services/update', method: 'PUT' };
BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };
getAllBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_ALL;
return this.makeRequest(path, method);
}
getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_GET;
return this.makeRequest(path, method);
}
updateBlockedServices(config: any) {
const { path, method } = this.BLOCKED_SERVICES_UPDATE;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
}
// Settings for statistics
GET_STATS = { path: 'stats', method: 'GET' };
GET_STATS_CONFIG = { path: 'stats/config', method: 'GET' };
UPDATE_STATS_CONFIG = { path: 'stats/config/update', method: 'PUT' };
STATS_RESET = { path: 'stats_reset', method: 'POST' };
getStats(recent?: number) {
const { path, method } = this.GET_STATS;
const config = recent ? { params: { recent } } : undefined;
return this.makeRequest(path, method, config);
}
getStatsConfig() {
const { path, method } = this.GET_STATS_CONFIG;
return this.makeRequest(path, method);
}
setStatsConfig(data: any) {
const { path, method } = this.UPDATE_STATS_CONFIG;
const config = {
data,
};
return this.makeRequest(path, method, config);
}
resetStats() {
const { path, method } = this.STATS_RESET;
return this.makeRequest(path, method);
}
// Query log
GET_QUERY_LOG = { path: 'querylog', method: 'GET' };
UPDATE_QUERY_LOG_CONFIG = { path: 'querylog/config/update', method: 'PUT' };
GET_QUERY_LOG_CONFIG = { path: 'querylog/config', method: 'GET' };
QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' };
getQueryLog(params: any) {
const { path, method } = this.GET_QUERY_LOG;
const url = getPathWithQueryString(path, {
...params,
limit: QUERY_LOGS_PAGE_LIMIT,
});
return this.makeRequest(url, method);
}
getQueryLogConfig() {
const { path, method } = this.GET_QUERY_LOG_CONFIG;
return this.makeRequest(path, method);
}
setQueryLogConfig(data: any) {
const { path, method } = this.UPDATE_QUERY_LOG_CONFIG;
const config = {
data,
};
return this.makeRequest(path, method, config);
}
clearQueryLog() {
const { path, method } = this.QUERY_LOG_CLEAR;
return this.makeRequest(path, method);
}
// Login
LOGIN = { path: 'login', method: 'POST' };
login(data: any) {
const { path, method } = this.LOGIN;
const config = {
data,
};
return this.makeRequest(path, method, config);
}
// Logout
LOGOUT_PATH = 'logout';
getLogoutUrl() {
return `${this.baseUrl}/${this.LOGOUT_PATH}`;
}
// Profile
GET_PROFILE = { path: 'profile', method: 'GET' };
UPDATE_PROFILE = { path: 'profile/update', method: 'PUT' };
getProfile() {
const { path, method } = this.GET_PROFILE;
return this.makeRequest(path, method);
}
setProfile(data: any) {
const theme = data.theme ? data.theme : THEMES.auto;
const defaultLanguage = intl.getUILanguage() ? intl.getUILanguage() : 'en';
const language = data.language ? data.language : defaultLanguage;
const { path, method } = this.UPDATE_PROFILE;
const config = { data: { theme, language } };
return this.makeRequest(path, method, config);
}
// DNS config
GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' };
SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' };
getDnsConfig() {
const { path, method } = this.GET_DNS_CONFIG;
return this.makeRequest(path, method);
}
setDnsConfig(data: any) {
const { path, method } = this.SET_DNS_CONFIG;
const config = {
data,
};
return this.makeRequest(path, method, config);
}
SET_PROTECTION = { path: 'protection', method: 'POST' };
setProtection(data: any) {
const { enabled, duration } = data;
const { path, method } = this.SET_PROTECTION;
return this.makeRequest(path, method, { data: { enabled, duration } });
}
// Cache
CLEAR_CACHE = { path: 'cache_clear', method: 'POST' };
clearCache() {
const { path, method } = this.CLEAR_CACHE;
return this.makeRequest(path, method);
}
}
export const apiClient = new Api(BASE_URL);

View file

@ -0,0 +1,78 @@
import { HTML_PAGES, R_PATH_LAST_PART } from '../helpers/constants';
type CustomFetchOptions = RequestInit & {
skipAuthRedirect?: boolean;
};
/**
* Custom fetch mutator for orval-generated API client.
*
* Custom fetch wrapper providing AdGuard Home-specific behaviors:
* - Sets `Content-Type: application/json` on requests with a body
* - Parses response as JSON (falls back to raw text if JSON.parse fails)
* - On 403 and not on login/install page redirects browser to login
* and returns `false` (does not throw)
* - On 403 while on login/install page throws Error normally
* - Throws Error with format `${path} | ${data} | ${status}` for all
* other non-ok responses
* - On empty response body returns empty string ''
* - On 204 returns empty string ''
*/
export const customFetch = async <T>(url: string, options?: CustomFetchOptions): Promise<T> => {
const { skipAuthRedirect, ...fetchOptions } = options || {};
const fullUrl = url;
const headers: Record<string, string> = {};
// Preserve any headers passed in options
if (fetchOptions.headers) {
const incomingHeaders = fetchOptions.headers as Record<string, string>;
Object.assign(headers, incomingHeaders);
}
try {
const response = await fetch(fullUrl, {
...fetchOptions,
headers,
});
const text = await response.text();
const data: T = text
? (() => {
try {
return JSON.parse(text);
} catch {
return text;
}
})()
: ('' as unknown as T);
if (!response.ok) {
const { pathname } = document.location;
const shouldRedirect = pathname !== HTML_PAGES.LOGIN && pathname !== HTML_PAGES.INSTALL;
if (response.status === 403 && shouldRedirect && !skipAuthRedirect) {
const loginPageUrl = window.location.href.replace(
R_PATH_LAST_PART,
HTML_PAGES.LOGIN,
);
window.location.replace(loginPageUrl);
return false as unknown as T;
}
throw new Error(
`${fullUrl} | ${typeof data === 'string' ? data : JSON.stringify(data)} | ${response.status}`,
);
}
return data;
} catch (error) {
if (error instanceof Error && error.message.includes('|')) {
throw error;
}
throw new Error(`${fullUrl} | ${error instanceof Error ? error.message : String(error)}`);
}
};
export default customFetch;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
/**
* Client and host access list. Each of the lists should contain only unique elements. In addition, allowed and disallowed lists cannot contain the same elements.
*/
export interface AccessList {
/** The allowlist of clients: IP addresses, CIDRs, or ClientIDs. */
allowed_clients?: string[];
/** The blocklist of clients: IP addresses, CIDRs, or ClientIDs. */
disallowed_clients?: string[];
/** The blocklist of hosts. */
blocked_hosts?: string[];
}

View file

@ -0,0 +1,3 @@
import type { AccessList } from './accessList';
export type AccessListResponse = AccessList;

View file

@ -0,0 +1,3 @@
import type { AccessList } from './accessList';
export type AccessSetRequest = AccessList;

View file

@ -0,0 +1,9 @@
/**
* /add_url request data
*/
export interface AddUrlRequest {
name?: string;
/** URL or an absolute path to the file containing filtering rules. */
url?: string;
whitelist?: boolean;
}

View file

@ -0,0 +1,7 @@
/**
* Port information
*/
export interface AddressInfo {
ip: string;
port: number;
}

View file

@ -0,0 +1,11 @@
import type { NetInterfaces } from './netInterfaces';
/**
* AdGuard Home addresses configuration
*/
export interface AddressesInfo {
dns_port: number;
interfaces: NetInterfaces;
version: string;
web_port: number;
}

View file

@ -0,0 +1,12 @@
export interface BlockedService {
/** The SVG icon as a Base64-encoded string to make it easier to embed it into a data URL. */
icon_svg: string;
/** The ID of this service. */
id: string;
/** The human-readable name of this service. */
name: string;
/** The array of the filtering rules. */
rules: string[];
/** The ID of the group, that the service belongs to. */
group_id?: string;
}

View file

@ -0,0 +1,7 @@
import type { BlockedService } from './blockedService';
import type { ServiceGroup } from './serviceGroup';
export interface BlockedServicesAll {
blocked_services: BlockedService[];
groups: ServiceGroup[];
}

View file

@ -0,0 +1 @@
export type BlockedServicesArray = string[];

View file

@ -0,0 +1,7 @@
import type { Schedule } from './schedule';
export interface BlockedServicesSchedule {
schedule?: Schedule;
/** The names of the blocked services. */
ids?: string[];
}

View file

@ -0,0 +1,12 @@
import type { CheckConfigRequestInfo } from './checkConfigRequestInfo';
import type { Lang } from './lang';
/**
* Configuration to be checked
*/
export interface CheckConfigRequest {
dns?: CheckConfigRequestInfo;
language?: Lang;
set_static_ip?: boolean;
web?: CheckConfigRequestInfo;
}

View file

@ -0,0 +1,5 @@
export interface CheckConfigRequestInfo {
ip?: string;
port?: number;
autofix?: boolean;
}

View file

@ -0,0 +1,9 @@
import type { CheckConfigResponseInfo } from './checkConfigResponseInfo';
import type { CheckConfigStaticIpInfo } from './checkConfigStaticIpInfo';
export interface CheckConfigResponse {
dns: CheckConfigResponseInfo;
language: CheckConfigResponseInfo;
static_ip: CheckConfigStaticIpInfo;
web: CheckConfigResponseInfo;
}

View file

@ -0,0 +1,4 @@
export interface CheckConfigResponseInfo {
status: string;
can_autofix: boolean;
}

View file

@ -0,0 +1,9 @@
import type { CheckConfigStaticIpInfoStatic } from './checkConfigStaticIpInfoStatic';
export interface CheckConfigStaticIpInfo {
static?: CheckConfigStaticIpInfoStatic;
/** Current dynamic IP address. Set if static=no */
ip?: string;
/** Error text. Set if static=error */
error?: string;
}

View file

@ -0,0 +1,4 @@
/**
* Can be: yes, no, error
*/
export type CheckConfigStaticIpInfoStatic = 'yes' | 'no' | 'error';

View file

@ -0,0 +1,63 @@
import type { SafeSearchConfig } from './safeSearchConfig';
import type { Schedule } from './schedule';
/**
* Client information.
*/
export interface Client {
/** Name */
name?: string;
/** IP, CIDR, MAC, or ClientID. */
ids?: string[];
use_global_settings?: boolean;
filtering_enabled?: boolean;
parental_enabled?: boolean;
safebrowsing_enabled?: boolean;
/** @deprecated */
safesearch_enabled?: boolean;
safe_search?: SafeSearchConfig;
use_global_blocked_services?: boolean;
blocked_services_schedule?: Schedule;
blocked_services?: string[];
upstreams?: string[];
tags?: string[];
/**
* NOTE: If `ignore_querylog` is not set in HTTP API `GET /clients/add`
* request then default value (false) will be used.
*
* If `ignore_querylog` is not set in HTTP API `GET /clients/update`
* request then the existing value will not be changed.
*
* This behaviour can be changed in the future versions.
*/
ignore_querylog?: boolean;
/**
* NOTE: If `ignore_statistics` is not set in HTTP API `GET
* /clients/add` request then default value (false) will be used.
*
* If `ignore_statistics` is not set in HTTP API `GET /clients/update`
* request then the existing value will not be changed.
*
* This behaviour can be changed in the future versions.
*/
ignore_statistics?: boolean;
/**
* NOTE: If `upstreams_cache_enabled` is not set in HTTP API
* `GET /clients/add` request then default value (false) will be used.
*
* If `upstreams_cache_enabled` is not set in HTTP API
* `GET /clients/update` request then the existing value will not be
* changed.
*
* This behaviour can be changed in the future versions.
*/
upstreams_cache_enabled?: boolean;
/**
* NOTE: If `upstreams_cache_enabled` is not set in HTTP API
* `GET /clients/update` request then the existing value will not be
* changed.
*
* This behaviour can be changed in the future versions.
*/
upstreams_cache_size?: number;
}

View file

@ -0,0 +1,14 @@
import type { WhoisInfo } from './whoisInfo';
/**
* Auto-Client information
*/
export interface ClientAuto {
/** IP address */
ip?: string;
/** Name */
name?: string;
/** The source of this information */
source?: string;
whois_info?: WhoisInfo;
}

View file

@ -0,0 +1,6 @@
/**
* Client delete request
*/
export interface ClientDelete {
name?: string;
}

View file

@ -0,0 +1,29 @@
import type { SafeSearchConfig } from './safeSearchConfig';
import type { WhoisInfo } from './whoisInfo';
/**
* Client information.
*/
export interface ClientFindSubEntry {
/** Name */
name?: string;
/** IP, CIDR, MAC, or ClientID. */
ids?: string[];
use_global_settings?: boolean;
filtering_enabled?: boolean;
parental_enabled?: boolean;
safebrowsing_enabled?: boolean;
/** @deprecated */
safesearch_enabled?: boolean;
safe_search?: SafeSearchConfig;
use_global_blocked_services?: boolean;
blocked_services?: string[];
upstreams?: string[];
whois_info?: WhoisInfo;
/** Whether the client's IP is blocked or not. */
disallowed?: boolean;
/** The rule due to which the client is disallowed. If disallowed is set to true, and this string is empty, then the client IP is disallowed by the "allowed IP list", that is it is not included in the allowed list. */
disallowed_rule?: string;
ignore_querylog?: boolean;
ignore_statistics?: boolean;
}

View file

@ -0,0 +1,9 @@
import type { Client } from './client';
/**
* Client update request
*/
export interface ClientUpdate {
name?: string;
data?: Client;
}

View file

@ -0,0 +1,8 @@
import type { ClientsArray } from './clientsArray';
import type { ClientsAutoArray } from './clientsAutoArray';
export interface Clients {
clients?: ClientsArray;
auto_clients?: ClientsAutoArray;
supported_tags?: string[];
}

View file

@ -0,0 +1,6 @@
import type { Client } from './client';
/**
* Clients array
*/
export type ClientsArray = Client[];

View file

@ -0,0 +1,6 @@
import type { ClientAuto } from './clientAuto';
/**
* Auto-Clients array
*/
export type ClientsAutoArray = ClientAuto[];

View file

@ -0,0 +1,5 @@
import type { ClientFindSubEntry } from './clientFindSubEntry';
export interface ClientsFindEntry {
[key: string]: ClientFindSubEntry;
}

View file

@ -0,0 +1,7 @@
export type ClientsFindParams = {
/**
* Filter by IP address or ClientIDs. Parameters with names `ip1`, `ip2`, and so on are also accepted and interpreted as "ip0 OR ip1 OR ip2".
* TODO(a.garipov): Replace with a better query API.
*/
ip0?: string;
};

View file

@ -0,0 +1,6 @@
import type { ClientsFindEntry } from './clientsFindEntry';
/**
* Client search results.
*/
export type ClientsFindResponse = ClientsFindEntry[];

View file

@ -0,0 +1,8 @@
import type { ClientsSearchRequestItem } from './clientsSearchRequestItem';
/**
* Client search request
*/
export interface ClientsSearchRequest {
clients?: ClientsSearchRequestItem[];
}

View file

@ -0,0 +1,4 @@
export interface ClientsSearchRequestItem {
/** Client IP address, CIDR, MAC address, or ClientID */
id?: string;
}

View file

@ -0,0 +1,69 @@
import type { DNSConfigBlockingMode } from './dNSConfigBlockingMode';
import type { DNSConfigUpstreamMode } from './dNSConfigUpstreamMode';
/**
* DNS server configuration
*/
export interface DNSConfig {
/** Bootstrap servers, port is optional after colon. Empty value will reset it to default values. */
bootstrap_dns?: string[];
/** Upstream servers, port is optional after colon. Empty value will reset it to default values. */
upstream_dns?: string[];
/** List of fallback DNS servers used when upstream DNS servers are not responding. Empty value will clear the list. */
fallback_dns?: string[];
upstream_dns_file?: string;
protection_enabled?: boolean;
ratelimit?: number;
/**
* Length of the subnet mask for IPv4 addresses.
* @minimum 0
* @maximum 32
*/
ratelimit_subnet_subnet_len_ipv4?: number;
/**
* Length of the subnet mask for IPv6 addresses.
* @minimum 0
* @maximum 128
*/
ratelimit_subnet_subnet_len_ipv6?: number;
/** List of IP addresses excluded from rate limiting. */
ratelimit_whitelist?: string[];
blocking_mode?: DNSConfigBlockingMode;
blocking_ipv4?: string;
blocking_ipv6?: string;
/**
* TTL for blocked responses.
* @minimum 0
*/
blocked_response_ttl?: number;
/** Protection is pause until this time. Nullable. */
protection_disabled_until?: string;
edns_cs_enabled?: boolean;
edns_cs_use_custom?: boolean;
edns_cs_custom_ip?: string;
disable_ipv6?: boolean;
dnssec_enabled?: boolean;
cache_size?: number;
cache_ttl_min?: number;
cache_ttl_max?: number;
/**
* Enables or disables the DNS response cache.
*
* If `cache_enabled` is `true`, the companion field `cache_size` must
* be present and greater than 0, or the `dns.cache_size` setting in
* the configuration file must already be greater than 0.
*/
cache_enabled?: boolean;
cache_optimistic?: boolean;
/** Upstream modes enumeration. The empty string value is deprecated; use `load_balance` instead. */
upstream_mode?: DNSConfigUpstreamMode;
use_private_ptr_resolvers?: boolean;
resolve_clients?: boolean;
/** Upstream servers, port is optional after colon. Empty value will reset it to default values. */
local_ptr_upstreams?: string[];
/**
* The number of seconds to wait for a response from the upstream server
* @minimum 1
*/
upstream_timeout?: number;
}

View file

@ -0,0 +1 @@
export type DNSConfigBlockingMode = 'default' | 'refused' | 'nxdomain' | 'null_ip' | 'custom_ip';

View file

@ -0,0 +1,4 @@
/**
* Upstream modes enumeration. The empty string value is deprecated; use `load_balance` instead.
*/
export type DNSConfigUpstreamMode = '' | 'fastest_addr' | 'load_balance' | 'parallel';

View file

@ -0,0 +1,17 @@
/**
* The single interval within a day. It begins at the `start` and ends before the `end`.
*/
export interface DayRange {
/**
* The number of milliseconds elapsed from the start of a day. It must be less than `end` and is expected to be rounded to minutes. So the maximum value is `86340000` (23 hours and 59 minutes).
* @minimum 0
* @maximum 86340000
*/
start?: number;
/**
* The number of milliseconds elapsed from the start of a day. It is expected to be rounded to minutes. The maximum value is `86400000` (24 hours).
* @minimum 0
* @maximum 86400000
*/
end?: number;
}

View file

@ -0,0 +1,9 @@
import type { DhcpConfigV4 } from './dhcpConfigV4';
import type { DhcpConfigV6 } from './dhcpConfigV6';
export interface DhcpConfig {
enabled?: boolean;
interface_name?: string;
v4?: DhcpConfigV4;
v6?: DhcpConfigV6;
}

View file

@ -0,0 +1,7 @@
export interface DhcpConfigV4 {
gateway_ip?: string;
subnet_mask?: string;
range_start?: string;
range_end?: string;
lease_duration?: number;
}

View file

@ -0,0 +1,4 @@
export interface DhcpConfigV6 {
range_start?: string;
lease_duration?: number;
}

View file

@ -0,0 +1,7 @@
/**
* Request for checking for other DHCP servers in the network.
*/
export interface DhcpFindActiveReq {
/** The name of the network interface */
interface?: string;
}

View file

@ -0,0 +1,9 @@
/**
* DHCP lease information
*/
export interface DhcpLease {
mac: string;
ip: string;
hostname: string;
expires: string;
}

View file

@ -0,0 +1,10 @@
import type { DhcpSearchV4 } from './dhcpSearchV4';
import type { DhcpSearchV6 } from './dhcpSearchV6';
/**
* Information about a DHCP server discovered in the current network.
*/
export interface DhcpSearchResult {
v4?: DhcpSearchV4;
v6?: DhcpSearchV6;
}

View file

@ -0,0 +1,8 @@
import type { DhcpSearchResultOtherServerFound } from './dhcpSearchResultOtherServerFound';
export interface DhcpSearchResultOtherServer {
/** The result of searching the other DHCP server. */
found?: DhcpSearchResultOtherServerFound;
/** Set if found=error */
error?: string;
}

View file

@ -0,0 +1,4 @@
/**
* The result of searching the other DHCP server.
*/
export type DhcpSearchResultOtherServerFound = 'yes' | 'no' | 'error';

View file

@ -0,0 +1,8 @@
import type { DhcpSearchResultStaticIPStatic } from './dhcpSearchResultStaticIPStatic';
export interface DhcpSearchResultStaticIP {
/** The result of determining static IP address. */
static?: DhcpSearchResultStaticIPStatic;
/** Set if static=no */
ip?: string;
}

View file

@ -0,0 +1,4 @@
/**
* The result of determining static IP address.
*/
export type DhcpSearchResultStaticIPStatic = 'yes' | 'no' | 'error';

View file

@ -0,0 +1,7 @@
import type { DhcpSearchResultOtherServer } from './dhcpSearchResultOtherServer';
import type { DhcpSearchResultStaticIP } from './dhcpSearchResultStaticIP';
export interface DhcpSearchV4 {
other_server?: DhcpSearchResultOtherServer;
static_ip?: DhcpSearchResultStaticIP;
}

View file

@ -0,0 +1,5 @@
import type { DhcpSearchResultOtherServer } from './dhcpSearchResultOtherServer';
export interface DhcpSearchV6 {
other_server?: DhcpSearchResultOtherServer;
}

View file

@ -0,0 +1,8 @@
/**
* DHCP static lease information
*/
export interface DhcpStaticLease {
mac: string;
ip: string;
hostname: string;
}

View file

@ -0,0 +1,3 @@
import type { DhcpStaticLease } from './dhcpStaticLease';
export type DhcpStaticLeaseBody = DhcpStaticLease;

View file

@ -0,0 +1,16 @@
import type { DhcpConfigV4 } from './dhcpConfigV4';
import type { DhcpConfigV6 } from './dhcpConfigV6';
import type { DhcpLease } from './dhcpLease';
import type { DhcpStaticLease } from './dhcpStaticLease';
/**
* Built-in DHCP server configuration and status
*/
export interface DhcpStatus {
enabled?: boolean;
interface_name?: string;
v4?: DhcpConfigV4;
v6?: DhcpConfigV6;
leases: DhcpLease[];
static_leases?: DhcpStaticLease[];
}

View file

@ -0,0 +1,8 @@
/**
* DNS answer section
*/
export interface DnsAnswer {
ttl?: number;
type?: string;
value?: string;
}

View file

@ -0,0 +1,5 @@
import type { DNSConfig } from './dNSConfig';
export type DnsInfo200 = DNSConfig & {
default_local_ptr_upstreams?: string[];
};

View file

@ -0,0 +1,9 @@
/**
* DNS question section
*/
export interface DnsQuestion {
class?: string;
name?: string;
unicode_name?: string;
type?: string;
}

View file

@ -0,0 +1,7 @@
/**
* A generic JSON error response.
*/
export interface Error {
/** The error message, an opaque string. */
message?: string;
}

View file

@ -0,0 +1,11 @@
/**
* Filter subscription info
*/
export interface Filter {
enabled: boolean;
id: number;
last_updated?: string;
name: string;
rules_count: number;
url: string;
}

View file

@ -0,0 +1,29 @@
import type { FilteringReason } from './filteringReason';
import type { ResultRule } from './resultRule';
/**
* Check Host Result
*/
export interface FilterCheckHostResponse {
reason?: FilteringReason;
/**
* In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to.
* Deprecated: use `rules[*].filter_list_id` instead.
* @deprecated
*/
filter_id?: number;
/**
* Filtering rule applied to the request (if any).
* Deprecated: use `rules[*].text` instead.
* @deprecated
*/
rule?: string;
/** Applied rules. */
rules?: ResultRule[];
/** Set if reason=FilteredBlockedService */
service_name?: string;
/** Set if reason=Rewrite */
cname?: string;
/** Set if reason=Rewrite */
ip_addrs?: string[];
}

View file

@ -0,0 +1,7 @@
/**
* Filtering settings
*/
export interface FilterConfig {
enabled?: boolean;
interval?: number;
}

View file

@ -0,0 +1,6 @@
/**
* Refresh Filters request data
*/
export interface FilterRefreshRequest {
whitelist?: boolean;
}

View file

@ -0,0 +1,6 @@
/**
* /filtering/refresh response data
*/
export interface FilterRefreshResponse {
updated?: number;
}

View file

@ -0,0 +1,10 @@
import type { FilterSetUrlData } from './filterSetUrlData';
/**
* Filtering URL settings
*/
export interface FilterSetUrl {
data?: FilterSetUrlData;
url?: string;
whitelist?: boolean;
}

View file

@ -0,0 +1,8 @@
/**
* Filter update data
*/
export interface FilterSetUrlData {
enabled: boolean;
name: string;
url: string;
}

View file

@ -0,0 +1,12 @@
import type { Filter } from './filter';
/**
* Filtering settings
*/
export interface FilterStatus {
enabled?: boolean;
interval?: number;
filters?: Filter[];
whitelist_filters?: Filter[];
user_rules?: string[];
}

View file

@ -0,0 +1,14 @@
export type FilteringCheckHostParams = {
/**
* Filter by host name
*/
name: string;
/**
* Optional ClientID or client IP address
*/
client?: string;
/**
* Optional DNS type
*/
qtype?: string;
};

View file

@ -0,0 +1,16 @@
/**
* Request filtering status.
*/
export type FilteringReason =
| 'NotFilteredNotFound'
| 'NotFilteredWhiteList'
| 'NotFilteredError'
| 'FilteredBlackList'
| 'FilteredSafeBrowsing'
| 'FilteredParental'
| 'FilteredInvalid'
| 'FilteredSafeSearch'
| 'FilteredBlockedService'
| 'Rewrite'
| 'RewriteEtcHosts'
| 'RewriteRule';

View file

@ -0,0 +1,15 @@
/**
* Query log configuration
*/
export interface GetQueryLogConfigResponse {
/** Is query log enabled */
enabled: boolean;
/** Time period for query log rotation in milliseconds. */
interval: number;
/** Anonymize clients' IP addresses */
anonymize_client_ip: boolean;
/** List of host names, which should not be written to log */
ignored: string[];
/** If true, the host names in the `ignored` array are excluded from the query log. */
ignored_enabled?: boolean;
}

View file

@ -0,0 +1,13 @@
/**
* Statistics configuration
*/
export interface GetStatsConfigResponse {
/** Are statistics enabled */
enabled: boolean;
/** Statistics rotation interval in milliseconds */
interval: number;
/** List of host names, which should not be counted */
ignored: string[];
/** If true, the host names in the `ignored` array are excluded from the statistics. */
ignored_enabled?: boolean;
}

View file

@ -0,0 +1,7 @@
/**
* /version.json request data
*/
export interface GetVersionRequest {
/** If false, server will check for a new version data only once in several hours. */
recheck_now?: boolean;
}

View file

@ -0,0 +1,116 @@
export * from './accessList';
export * from './accessListResponse';
export * from './accessSetRequest';
export * from './addressesInfo';
export * from './addressInfo';
export * from './addUrlRequest';
export * from './blockedService';
export * from './blockedServicesAll';
export * from './blockedServicesArray';
export * from './blockedServicesSchedule';
export * from './checkConfigRequest';
export * from './checkConfigRequestInfo';
export * from './checkConfigResponse';
export * from './checkConfigResponseInfo';
export * from './checkConfigStaticIpInfo';
export * from './checkConfigStaticIpInfoStatic';
export * from './client';
export * from './clientAuto';
export * from './clientDelete';
export * from './clientFindSubEntry';
export * from './clients';
export * from './clientsArray';
export * from './clientsAutoArray';
export * from './clientsFindEntry';
export * from './clientsFindParams';
export * from './clientsFindResponse';
export * from './clientsSearchRequest';
export * from './clientsSearchRequestItem';
export * from './clientUpdate';
export * from './dayRange';
export * from './dhcpConfig';
export * from './dhcpConfigV4';
export * from './dhcpConfigV6';
export * from './dhcpFindActiveReq';
export * from './dhcpLease';
export * from './dhcpSearchResult';
export * from './dhcpSearchResultOtherServer';
export * from './dhcpSearchResultOtherServerFound';
export * from './dhcpSearchResultStaticIP';
export * from './dhcpSearchResultStaticIPStatic';
export * from './dhcpSearchV4';
export * from './dhcpSearchV6';
export * from './dhcpStaticLease';
export * from './dhcpStaticLeaseBody';
export * from './dhcpStatus';
export * from './dnsAnswer';
export * from './dNSConfig';
export * from './dNSConfigBlockingMode';
export * from './dNSConfigUpstreamMode';
export * from './dnsInfo200';
export * from './dnsQuestion';
export * from './error';
export * from './filter';
export * from './filterCheckHostResponse';
export * from './filterConfig';
export * from './filteringCheckHostParams';
export * from './filteringReason';
export * from './filterRefreshRequest';
export * from './filterRefreshResponse';
export * from './filterSetUrl';
export * from './filterSetUrlData';
export * from './filterStatus';
export * from './getQueryLogConfigResponse';
export * from './getStatsConfigResponse';
export * from './getVersionRequest';
export * from './initialConfiguration';
export * from './lang';
export * from './languageSettings';
export * from './login';
export * from './mobileConfigDoHParams';
export * from './mobileConfigDoTParams';
export * from './netInterface';
export * from './netInterfaces';
export * from './parentalStatus200';
export * from './profileInfo';
export * from './profileInfoTheme';
export * from './putQueryLogConfigUpdateRequest';
export * from './putStatsConfigUpdateRequest';
export * from './queryLog';
export * from './queryLogConfig';
export * from './queryLogConfigInterval';
export * from './queryLogItem';
export * from './queryLogItemClient';
export * from './queryLogItemClientProto';
export * from './queryLogItemClientWhois';
export * from './queryLogParams';
export * from './queryLogResponseStatus';
export * from './removeUrlRequest';
export * from './resultRule';
export * from './rewriteEntry';
export * from './rewriteEntryBody';
export * from './rewriteList';
export * from './rewriteSettings';
export * from './rewriteSettingsBody';
export * from './rewriteUpdate';
export * from './rewriteUpdateBody';
export * from './safebrowsingStatus200';
export * from './safeSearchConfig';
export * from './schedule';
export * from './serverStatus';
export * from './serviceGroup';
export * from './setProtectionRequest';
export * from './setRulesRequest';
export * from './stats';
export * from './statsConfig';
export * from './statsConfigInterval';
export * from './statsParams';
export * from './statsTimeUnits';
export * from './tlsConfig';
export * from './tlsConfigBody';
export * from './tlsConfigKeyType';
export * from './topArrayEntry';
export * from './upstreamsConfig';
export * from './upstreamsConfigResponse';
export * from './versionInfo';
export * from './whoisInfo';

View file

@ -0,0 +1,15 @@
import type { AddressInfo } from './addressInfo';
import type { Lang } from './lang';
/**
* AdGuard Home initial configuration for the first-install wizard.
*/
export interface InitialConfiguration {
dns: AddressInfo;
web: AddressInfo;
language?: Lang;
/** Basic auth password */
password: string;
/** Basic auth username */
username: string;
}

View file

@ -0,0 +1,40 @@
/**
* Language code.
*/
export type Lang =
| 'ar'
| 'be'
| 'bg'
| 'cs'
| 'da'
| 'de'
| 'en'
| 'es'
| 'fa'
| 'fi'
| 'fr'
| 'hr'
| 'hu'
| 'id'
| 'it'
| 'ja'
| 'ko'
| 'nl'
| 'no'
| 'pl'
| 'pt-br'
| 'pt-pt'
| 'ro'
| 'ru'
| 'si-lk'
| 'sk'
| 'sl'
| 'sr-cs'
| 'sv'
| 'th'
| 'tr'
| 'uk'
| 'vi'
| 'zh-cn'
| 'zh-hk'
| 'zh-tw';

View file

@ -0,0 +1,8 @@
import type { Lang } from './lang';
/**
* Language settings object.
*/
export interface LanguageSettings {
language: Lang;
}

View file

@ -0,0 +1,9 @@
/**
* Login request data
*/
export interface Login {
/** User name */
name?: string;
/** Password */
password?: string;
}

View file

@ -0,0 +1,10 @@
export type MobileConfigDoHParams = {
/**
* Host for which the config is generated. If no host is provided, `tls.server_name` from the configuration file is used. If `tls.server_name` is not set, the API returns an error with a 500 status.
*/
host: string;
/**
* ClientID.
*/
client_id?: string;
};

View file

@ -0,0 +1,10 @@
export type MobileConfigDoTParams = {
/**
* Host for which the config is generated. If no host is provided, `tls.server_name` from the configuration file is used. If `tls.server_name` is not set, the API returns an error with a 500 status.
*/
host: string;
/**
* ClientID.
*/
client_id?: string;
};

View file

@ -0,0 +1,15 @@
/**
* Network interface info
*/
export interface NetInterface {
/** Flags could be any combination of the following values, divided by the "|" character: "up", "broadcast", "loopback", "pointtopoint" and "multicast". */
flags: string;
/** The IP address of the gateway. */
gateway_ip: string;
hardware_address: string;
/** The addresses of the interface of v4 family. */
ipv4_addresses: string[];
/** The addresses of the interface of v6 family. */
ipv6_addresses: string[];
name: string;
}

View file

@ -0,0 +1,8 @@
import type { NetInterface } from './netInterface';
/**
* Network interfaces dictionary, keys are interface names.
*/
export interface NetInterfaces {
[key: string]: NetInterface;
}

View file

@ -0,0 +1,4 @@
export type ParentalStatus200 = {
enable?: boolean;
sensitivity?: number;
};

View file

@ -0,0 +1,12 @@
import type { Lang } from './lang';
import type { ProfileInfoTheme } from './profileInfoTheme';
/**
* Information about the current user
*/
export interface ProfileInfo {
name: string;
language: Lang;
/** Interface theme */
theme: ProfileInfoTheme;
}

View file

@ -0,0 +1,4 @@
/**
* Interface theme
*/
export type ProfileInfoTheme = 'auto' | 'dark' | 'light';

View file

@ -0,0 +1,3 @@
import type { GetQueryLogConfigResponse } from './getQueryLogConfigResponse';
export type PutQueryLogConfigUpdateRequest = GetQueryLogConfigResponse;

View file

@ -0,0 +1,3 @@
import type { GetStatsConfigResponse } from './getStatsConfigResponse';
export type PutStatsConfigUpdateRequest = GetStatsConfigResponse;

Some files were not shown because too many files have changed in this diff Show more