From b98981a172100548860f70a857bd4c8de9e13a61 Mon Sep 17 00:00:00 2001 From: Rekryt Date: Thu, 10 Jul 2025 18:17:05 +0300 Subject: [PATCH] feat: new portal selection and favicons --- composer.json | 3 +- frontend/components/base/Form.vue | 4 +- frontend/components/base/Select.vue | 128 +++++ frontend/components/base/form/portals.vue | 82 +--- frontend/components/core/AppBar.vue | 2 +- frontend/nuxt.config.ts | 13 +- public/200.html | 135 ------ public/404.html | 135 ------ public/_nuxt/builds/latest.json | 2 +- public/_nuxt/index.ByG5L0aj.css | 1 - public/_payload.json | 1 - public/about/_payload.json | 1 - public/about/index.html | 503 -------------------- public/cn/_payload.json | 1 - public/cn/about/_payload.json | 1 - public/cn/about/index.html | 503 -------------------- public/cn/index.html | 503 -------------------- public/index.html | 503 -------------------- public/manifest.json | 1 - public/ru/_payload.json | 1 - public/ru/about/_payload.json | 1 - public/ru/about/index.html | 503 -------------------- public/ru/index.html | 503 -------------------- src/App/Controller/FaviconController.php | 123 +++++ src/Infrastructure/API/Server.php | 7 +- src/Infrastructure/Storage/IconsStorage.php | 42 ++ 26 files changed, 338 insertions(+), 3364 deletions(-) create mode 100644 frontend/components/base/Select.vue delete mode 100644 public/200.html delete mode 100644 public/404.html delete mode 100644 public/_nuxt/index.ByG5L0aj.css delete mode 100644 public/_payload.json delete mode 100644 public/about/_payload.json delete mode 100644 public/about/index.html delete mode 100644 public/cn/_payload.json delete mode 100644 public/cn/about/_payload.json delete mode 100644 public/cn/about/index.html delete mode 100644 public/cn/index.html delete mode 100644 public/index.html delete mode 100644 public/manifest.json delete mode 100644 public/ru/_payload.json delete mode 100644 public/ru/about/_payload.json delete mode 100644 public/ru/about/index.html delete mode 100644 public/ru/index.html create mode 100644 src/App/Controller/FaviconController.php create mode 100644 src/Infrastructure/Storage/IconsStorage.php diff --git a/composer.json b/composer.json index 1119fbe..69544ca 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "amphp/dns": "^2.2", "vlucas/phpdotenv": "^5.5", "amphp/file": "^3.1", - "ext-fileinfo": "*" + "ext-fileinfo": "*", + "ext-libxml": "*" }, "autoload": { "files": [ diff --git a/frontend/components/base/Form.vue b/frontend/components/base/Form.vue index bafb0cd..2effdaa 100644 --- a/frontend/components/base/Form.vue +++ b/frontend/components/base/Form.vue @@ -332,7 +332,7 @@ const submit = () => { {{ t('groups') }} {{ t('exclude') }} - + { diff --git a/frontend/components/base/Select.vue b/frontend/components/base/Select.vue new file mode 100644 index 0000000..6730543 --- /dev/null +++ b/frontend/components/base/Select.vue @@ -0,0 +1,128 @@ + + +{ + "en": { + "cleanSelection": "Clear selection", + "collapseAll": "Collapse all", + "expandAll": "Expand all" + }, + "ru": { + "cleanSelection": "Очистить выбор", + "collapseAll": "Свернуть всё", + "expandAll": "Развернуть всё" + }, + "cn": { + "cleanSelection": "清除选择", + "collapseAll": "全部折叠", + "expandAll": "全部展开" + } +} + + + diff --git a/frontend/components/base/form/portals.vue b/frontend/components/base/form/portals.vue index cd4b59d..4208da8 100644 --- a/frontend/components/base/form/portals.vue +++ b/frontend/components/base/form/portals.vue @@ -1,5 +1,5 @@ { @@ -79,43 +79,7 @@ const setSelectGroup = (item: { label: string; items: { label: string; value: st } diff --git a/frontend/components/core/AppBar.vue b/frontend/components/core/AppBar.vue index 3a6d4a2..5e10efe 100644 --- a/frontend/components/core/AppBar.vue +++ b/frontend/components/core/AppBar.vue @@ -36,7 +36,7 @@ Star - + - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- \ No newline at end of file diff --git a/public/404.html b/public/404.html deleted file mode 100644 index 841bfd7..0000000 --- a/public/404.html +++ /dev/null @@ -1,135 +0,0 @@ - - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- \ No newline at end of file diff --git a/public/_nuxt/builds/latest.json b/public/_nuxt/builds/latest.json index 30a34b8..785c539 100644 --- a/public/_nuxt/builds/latest.json +++ b/public/_nuxt/builds/latest.json @@ -1 +1 @@ -{"id":"9b43bea9-0123-48f5-94e1-effdb17e4eb1","timestamp":1751480831928} \ No newline at end of file +{"id":"96dc3cda-1bf0-41dd-8b21-98f2698185ce","timestamp":1752160421393} \ No newline at end of file diff --git a/public/_nuxt/index.ByG5L0aj.css b/public/_nuxt/index.ByG5L0aj.css deleted file mode 100644 index 5fd815b..0000000 --- a/public/_nuxt/index.ByG5L0aj.css +++ /dev/null @@ -1 +0,0 @@ -.baseForm{max-width:580px} diff --git a/public/_payload.json b/public/_payload.json deleted file mode 100644 index adf165f..0000000 --- a/public/_payload.json +++ /dev/null @@ -1 +0,0 @@ -[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1751480910616] \ No newline at end of file diff --git a/public/about/_payload.json b/public/about/_payload.json deleted file mode 100644 index 18017ae..0000000 --- a/public/about/_payload.json +++ /dev/null @@ -1 +0,0 @@ -[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1751480911280] \ No newline at end of file diff --git a/public/about/index.html b/public/about/index.html deleted file mode 100644 index f1988ec..0000000 --- a/public/about/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
About
flag
English

IP Address Collection and Management Service

This service is designed to collect and update IP addresses (IPv4 and IPv6), as well as their CIDR zones for specified domains. It is an asynchronous PHP web server based on AMPHP and uses the Linux utilities whois and ipcalc. The service provides interfaces to retrieve lists of IP address zones of specified domains (IPv4 addresses, IPv6 addresses, as well as CIDRv4 and CIDRv6 zones) in various formats, including plain text, JSON, and script formats for importing into "Address List" on routers such as MikroTik (RouterOS), Keenetic KVAS\BAT, SwitchyOmega, Amnezia, and others.
- \ No newline at end of file diff --git a/public/cn/_payload.json b/public/cn/_payload.json deleted file mode 100644 index b05af4b..0000000 --- a/public/cn/_payload.json +++ /dev/null @@ -1 +0,0 @@ -[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1751480911289] \ No newline at end of file diff --git a/public/cn/about/_payload.json b/public/cn/about/_payload.json deleted file mode 100644 index 7d50f61..0000000 --- a/public/cn/about/_payload.json +++ /dev/null @@ -1 +0,0 @@ -[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1751480911285] \ No newline at end of file diff --git a/public/cn/about/index.html b/public/cn/about/index.html deleted file mode 100644 index 99e00f3..0000000 --- a/public/cn/about/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
关于项目
flag
简体中文

IP地址收集与管理服务

该服务用于收集和更新指定域名的 IP 地址(IPv4 和 IPv6)及其 CIDR 区段。它是一个基于 AMPHP 的异步 PHP Web 服务器,使用 Linux 工具 whois 和 ipcalc。该服务提供接口,以多种格式(包括纯文本、JSON,以及可用于 MikroTik(RouterOS)、Keenetic KVAS\BAT、SwitchyOmega、Amnezia 等路由器的“地址列表”导入脚本)获取指定域名的 IP 地址区段(IPv4 地址、IPv6 地址、CIDRv4 和 CIDRv6 区段)列表。
- \ No newline at end of file diff --git a/public/cn/index.html b/public/cn/index.html deleted file mode 100644 index 1a29797..0000000 --- a/public/cn/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
通过门户
flag
简体中文
JSON
所有数据
- \ No newline at end of file diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 5af69e0..0000000 --- a/public/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Portals
flag
English
JSON
All data
- \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index 9fb1ca0..0000000 --- a/public/manifest.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"iplist","short_name":"iplist","description":"","lang":"en","start_url":"/?standalone=true","display":"standalone","background_color":"#ffffff","theme_color":"#000000","icons":[{"src":"/_nuxt/icons/64x64.b614aaf9.png","type":"image/png","sizes":"64x64","purpose":"any"},{"src":"/_nuxt/icons/64x64.maskable.b614aaf9.png","type":"image/png","sizes":"64x64","purpose":"maskable"},{"src":"/_nuxt/icons/120x120.b614aaf9.png","type":"image/png","sizes":"120x120","purpose":"any"},{"src":"/_nuxt/icons/120x120.maskable.b614aaf9.png","type":"image/png","sizes":"120x120","purpose":"maskable"},{"src":"/_nuxt/icons/144x144.b614aaf9.png","type":"image/png","sizes":"144x144","purpose":"any"},{"src":"/_nuxt/icons/144x144.maskable.b614aaf9.png","type":"image/png","sizes":"144x144","purpose":"maskable"},{"src":"/_nuxt/icons/152x152.b614aaf9.png","type":"image/png","sizes":"152x152","purpose":"any"},{"src":"/_nuxt/icons/152x152.maskable.b614aaf9.png","type":"image/png","sizes":"152x152","purpose":"maskable"},{"src":"/_nuxt/icons/192x192.b614aaf9.png","type":"image/png","sizes":"192x192","purpose":"any"},{"src":"/_nuxt/icons/192x192.maskable.b614aaf9.png","type":"image/png","sizes":"192x192","purpose":"maskable"},{"src":"/_nuxt/icons/384x384.b614aaf9.png","type":"image/png","sizes":"384x384","purpose":"any"},{"src":"/_nuxt/icons/384x384.maskable.b614aaf9.png","type":"image/png","sizes":"384x384","purpose":"maskable"},{"src":"/_nuxt/icons/512x512.b614aaf9.png","type":"image/png","sizes":"512x512","purpose":"any"},{"src":"/_nuxt/icons/512x512.maskable.b614aaf9.png","type":"image/png","sizes":"512x512","purpose":"maskable"}]} \ No newline at end of file diff --git a/public/ru/_payload.json b/public/ru/_payload.json deleted file mode 100644 index f6c40f4..0000000 --- a/public/ru/_payload.json +++ /dev/null @@ -1 +0,0 @@ -[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1751480911287] \ No newline at end of file diff --git a/public/ru/about/_payload.json b/public/ru/about/_payload.json deleted file mode 100644 index 321bb67..0000000 --- a/public/ru/about/_payload.json +++ /dev/null @@ -1 +0,0 @@ -[{"data":1,"prerenderedAt":3},["ShallowReactive",2],{},1751480911283] \ No newline at end of file diff --git a/public/ru/about/index.html b/public/ru/about/index.html deleted file mode 100644 index f8f46d2..0000000 --- a/public/ru/about/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
О проекте
flag
Русский

Сервис сбора IP-адресов и CIDR зон

Данный сервис предназначен для сбора и обновления IP-адресов (IPv4 и IPv6), а также их CIDR зон для указанных доменов. Это асинхронный PHP веб-сервер на основе AMPHP и Linux-утилит whois и ipcalc. Сервис предоставляет интерфейсы для получения списков зон ip адресов указанных доменов (IPv4 адресов, IPv6 адресов, а также CIDRv4 и CIDRv6 зон) в различных форматах, включая текстовый, JSON, форматы скриптов для добавления в "Address List" на роутерах Mikrotik (RouterOS), Keenetic KVAS\BAT, SwitchyOmega, Amnezia и др.
- \ No newline at end of file diff --git a/public/ru/index.html b/public/ru/index.html deleted file mode 100644 index 31eea69..0000000 --- a/public/ru/index.html +++ /dev/null @@ -1,503 +0,0 @@ - - -IP Address Collection and Management Service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Порталы
flag
Русский
JSON
Все данные
- \ No newline at end of file diff --git a/src/App/Controller/FaviconController.php b/src/App/Controller/FaviconController.php new file mode 100644 index 0000000..4e49072 --- /dev/null +++ b/src/App/Controller/FaviconController.php @@ -0,0 +1,123 @@ +storage = IconsStorage::getInstance(); + } + + /** + * @return string + */ + public function getBody(): string { + $site = $this->request->getQueryParameter('site'); + + if ($this->storage->has($site)) { + return $this->output($this->storage->get($site)); + } + + if (in_array($site, array_keys($this->service->sites))) { + $content = file_get_contents('https://' . $site . '/favicon.ico'); + if ($content) { + $filename = $site . '.ico'; + $this->saveIcon($site, $filename, $content); + return $this->output($filename); + } + + $siteUrl = 'https://' . $site . '/'; + $indexBody = file_get_contents('https://' . $site . '/'); + if ($indexBody) { + $rawUrl = $this->extractFaviconHref($indexBody); + $url = parse_url($rawUrl); + $parts = explode('.', $url['path']); + $ext = end($parts); + + $content = file_get_contents($siteUrl . $url['path']); + if ($content) { + $filename = $site . '.' . $ext; + $this->saveIcon($site, $filename, $content); + return $this->output($filename); + } + } + + $filename = 'blank.png'; + $this->saveIcon($site, $filename); + return $this->output($filename); + } + + $this->setHttpStatus(HttpStatus::NOT_FOUND); + return ''; + } + + /** + * @param string $html + * @return ?string + */ + private function extractFaviconHref(string $html): ?string { + $dom = new \DOMDocument(); + + // Заглушка для подавления ошибок из-за невалидного HTML + \libxml_use_internal_errors(true); + $dom->loadHTML($html); + \libxml_clear_errors(); + + $links = $dom->getElementsByTagName('link'); + + foreach ($links as $link) { + $rel = strtolower($link->getAttribute('rel')); + if (in_array($rel, ['icon', 'shortcut icon', 'alternate icon'])) { + $href = $link->getAttribute('href'); + return $href !== '' ? $href : null; + } + } + + return null; + } + + /** + * @param string $site + * @param string $filename + * @param ?string $content + * @return void + */ + private function saveIcon(string $site, string $filename, ?string $content = null): void { + $this->storage->set($site, $filename); + if ($content) { + file_put_contents(PATH_ROOT . '/storage/icons/' . $filename, $content); + } + $this->storage->save(); + } + + /** + * @param string $path + * @return string + */ + private function output(string $filename): string { + $path = PATH_ROOT . '/storage/icons/' . $filename; + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $contentType = finfo_file($finfo, $path); + finfo_close($finfo); + + $this->setHeaders([ + 'Content-Type' => $contentType, + 'Cache-Control' => 'public, max-age=31536000, immutable', + 'Expires' => gmdate('D, d M Y H:i:s', strtotime('+1 year')) . ' GMT', + ]); + return read($path); + } +} diff --git a/src/Infrastructure/API/Server.php b/src/Infrastructure/API/Server.php index 3a26563..d7e7b3f 100644 --- a/src/Infrastructure/API/Server.php +++ b/src/Infrastructure/API/Server.php @@ -98,9 +98,10 @@ final class Server implements AppModuleInterface { // $this->bindContext //); $router = new Router($this->httpServer, $this->logger, $this->errorHandler); - $httpHandler = HTTPHandler::getInstance($this->logger)->getHandler(); - $router->addRoute('GET', '/', $httpHandler); - $router->addRoute('GET', '/{name:.+}', $httpHandler); + $httpHandlerInstance = HTTPHandler::getInstance($this->logger); + $router->addRoute('GET', '/', $httpHandlerInstance->getHandler('main')); + $router->addRoute('GET', '/favicon', $httpHandlerInstance->getHandler('favicon')); + $router->addRoute('GET', '/{name:.+}', $httpHandlerInstance->getHandler('main')); $router->setFallback(new DocumentRoot($this->httpServer, $this->errorHandler, PATH_ROOT . '/public')); $this->httpServer->start($router, $this->errorHandler); diff --git a/src/Infrastructure/Storage/IconsStorage.php b/src/Infrastructure/Storage/IconsStorage.php new file mode 100644 index 0000000..51be4d6 --- /dev/null +++ b/src/Infrastructure/Storage/IconsStorage.php @@ -0,0 +1,42 @@ +data = (array) json_decode(file_get_contents($path)) ?? []; + } + } + + public static function getInstance(): IconsStorage { + return self::$_instance ??= new self(); + } + + public function get(string $key): ?string { + return $this->data[$key] ?? null; + } + + public function set(string $key, mixed $value): bool { + $this->data[$key] = $value; + return true; + } + + public function has(string $key): bool { + return isset($this->data[$key]); + } + + public function save(): void { + file_put_contents(PATH_ROOT . '/storage/' . self::FILENAME, json_encode($this->data, JSON_PRETTY_PRINT)); + App::getLogger()->notice('Icons storage saved', [count($this->data) . ' items']); + } +}