feat(web): acrylic theme, node flags and icons, build info
Acrylic cards and topbar over a blurred background, country flags, ISP and OS icons, per-node version tags, a build info footer and an admin versions tab. Overview cards drop the sparkline and widen the hover area.
This commit is contained in:
1 parent
1e5c660d05
commit
83f54978d2
29 files changed
+1310
-143
No files matched your search
+11
-1
@@ -85,6 +85,15 @@
|
||||
|
||||
`host` 只有汇总的数字:CPU 占用、内存与根分区的已用和总量、网卡累计收发字节与瞬时网速、系统运行时长。**没有 CPU 型号、主机名、网卡名,也没有任何 IP**——Agent 不采集这些,公开接口当然也不会返回;有测试断言响应里不出现这类字段。实时更新走 SSE 的 `hosts` 主题,历史每个 Agent 每分钟存一个样本(`retention.host_days`,默认 7 天)。
|
||||
|
||||
### 2.3.2 节点的展示信息
|
||||
|
||||
`GET /api/public/nodes`:看得见的 Agent 和没被吊销的 Probe 的展示信息,`{id, kind, os, os_name, arch, country, isp_key, isp_name}`——系统(`linux` / `windows` / `macos`)、「Debian 12」这样的系统名、架构(`x64` / `arm64`…)、国家代码(`CN`)、运营商类别(`mobile` / `telecom` / `unicom` / `broadnet` / `other`)和名字。**没有 IP 地址、城市、ASN、经纬度**,有测试保证:公开接口的整个响应里不出现 IP 形状的字符串,也没有 `ip`、`city`、`asn` 这些字段。管理员在设置里打开 `site.show_build` 之后,每项还有 `version` 和 `tag`(节点自己上报的程序版本和 tag);关着时这两个字段是 `null`。
|
||||
|
||||
`GET /api/public/about`:`{show_build, backend}`。`show_build` 关着时 `backend` 是 `null`;打开后 `backend` 是 `{version, tag, built_at}`(后端自己的版本、tag、构建时间,不含提交号)。
|
||||
|
||||
- 系统和架构是节点自报的:Agent 在 `fleet.hello` 里,Probe 随列表同步(`monitor.probe.agents` 的 `platform`)告诉 Agent,Agent 再放进心跳的 `probes[]`。只有粗粒度的名字,没有主机名、内核版本、CPU 型号。
|
||||
- 国家和运营商由 **Backend** 用节点的来源地址向 IP 归属服务查一次(`Monitor__GeoLookupUrl`,缺省 `https://ip.007995.xyz`,留空就不查):Probe 用 Agent 看到的来源 IP,Agent 用它的公开地址。**Probe 和 Agent 不访问任何外部服务。** 只查公网地址(回环、内网、CGNAT、链路本地一律不发出去);同一个地址一周内只查一次,失败 10 分钟后再试;查到的只留国家代码和运营商,地址只存在 Backend 内部的 `node_meta.geo_host`,不对外。
|
||||
|
||||
### 2.4 测试结果与目录
|
||||
|
||||
- `GET /api/public/tests/{test_id}`:包括计划、参数、状态、阶段,`metrics` 与 `scores`,`series`(例如码率–丢包曲线),以及置信说明 `caveats`。
|
||||
@@ -125,7 +134,8 @@
|
||||
| GET | `/api/admin/releases` | 各应用、各通道的当前版本,以及节点的版本分布(由 `Fleet.Server.Releases` 提供) |
|
||||
| POST | `/api/admin/releases/{app}/{channel}/rollout` | `{version, batches:[{labels?, count?}], soak_minutes}`:分批灰度;只能选择已经签名的清单 |
|
||||
| POST | `/api/admin/releases/{app}/approve` | 对 `policy = notify` 的节点批准更新 `{version, agents?}` |
|
||||
| GET/PATCH | `/api/admin/settings` | 站点公开或私有、保留期等 |
|
||||
| GET/PATCH | `/api/admin/settings` | 站点公开或私有、是否对访客显示构建信息(`site.show_build`)、保留期等 |
|
||||
| GET | `/api/admin/build-info` | 后端和每个 Agent、Probe 的版本、tag、提交、构建时间、系统(管理端「系统 → 版本」) |
|
||||
| GET/POST/PATCH | `/api/admin/users` | 首次启动时通过命令行或环境变量创建初始管理员 |
|
||||
|
||||
任务的请求体示例:
|
||||
|
||||
@@ -9,6 +9,8 @@ scripts/publish.sh <agent|probe|backend|fleet-enroll> <rid> → backend/art
|
||||
scripts/package.sh <agent|probe> <rid> → backend/artifacts/packages/monitor-<role>-<version>-<rid>.tar.gz(附 .sha256)
|
||||
```
|
||||
|
||||
构建信息(程序里显示的 tag、提交、构建时间):`publish.sh` 会用 `git describe --tags --always --dirty`、`git rev-parse` 和当前时间自动设置环境变量 `MONITOR_TAG`、`MONITOR_COMMIT`、`MONITOR_BUILT_AT`,直接用 `dotnet publish` 或 CI 构建时自己设置它们(容器的 Fastlane 构建用同名的 `--build-arg`);不设置就是空,本地构建照常可重复。版本号取自 `backend/Directory.Build.props` 的 `Version`。前端的信息由 `web/build-info.mjs` 在构建时写入(版本取自 `web/package.json`)。
|
||||
|
||||
| rid | 目标 | 构建方式 |
|
||||
| --- | --- | --- |
|
||||
| `linux-x64`、`linux-arm64` | Debian/Ubuntu glibc 服务器 | Windows CI 主机通过 AotAnywhere 交叉编译 |
|
||||
@@ -154,6 +156,8 @@ OpenWrt 默认不带 OpenSSL:`opkg install libopenssl3`(较新的快照和 2
|
||||
- VPS:`/etc/systemd/system/monitor-agent.service`,`Restart=always`,加 `NoNewPrivileges`、`ProtectSystem=strict`、`ReadWritePaths=/opt/monitor`、`ProtectHome`、`PrivateTmp`。这是不走安装脚本的手动方式;正式安装用上面的 `packaging/systemd`。
|
||||
- 日志进 journal:`journalctl -u monitor-agent`、`journalctl --user -u monitor-probe`。重启 Backend 用 `systemctl` 而不是手动再起一个进程,否则会端口冲突。
|
||||
|
||||
**国家和运营商**:Backend 用 Probe、Agent 的来源地址向 IP 归属服务查一次,展示在国旗和运营商图标上(`Monitor__GeoLookupUrl`,缺省 `https://ip.007995.xyz`;留空就不查;这个服务只供公益使用,署名和使用条款由部署的人自己遵守)。Probe、Agent 自己不访问外部服务,所以客户的防火墙审计里不会多出出站连接;Backend 发出去的只有公网 IP(内网地址不发)。
|
||||
|
||||
**从别处看这个面板**:本机跑前端,在页面里把数据源指向这个域名,用 API 令牌访问,Backend 设 `Monitor__CorsOrigins` 放行前端的来源(见 [web/README.md](../web/README.md))。
|
||||
|
||||
实测(Probe 在广东移动家宽,Agent 在日本 IIJ):
|
||||
|
||||
@@ -5,6 +5,7 @@ import { api } from '@shared/api/client'
|
||||
import type { AgentDto, CreatedJoinTokenDto, JoinTokenDto, Page } from '@shared/api/types'
|
||||
import { fmtAgo, fmtTime } from '@shared/format'
|
||||
import { useQuery } from '@shared/hooks'
|
||||
import { NodeTag, useNodeInfos } from '@shared/nodes'
|
||||
import { Chip, CommandTabs, Confirm, CopyButton, Dialog, Dot, Empty, ErrorNotice, Field, guessPlatform, Segmented, Spinner, useAction } from '@shared/ui'
|
||||
|
||||
export const STATUS: Record<string, { label: string; tone: 'ok' | 'warn' | 'bad' }> = {
|
||||
@@ -136,6 +137,7 @@ function AddAgentDialog({ onClose, onCreated }: { onClose: () => void; onCreated
|
||||
export function Agents() {
|
||||
const navigate = useNavigate()
|
||||
const agents = useQuery(() => api.get<Page<AgentDto>>('/api/admin/agents'), [], 10_000)
|
||||
const infos = useNodeInfos()
|
||||
const tokens = useQuery(() => api.get<Page<JoinTokenDto>>('/api/admin/join-tokens'), [])
|
||||
const [filter, setFilter] = useState<'all' | 'pending' | 'disabled'>('all')
|
||||
const [adding, setAdding] = useState(false)
|
||||
@@ -193,7 +195,12 @@ export function Agents() {
|
||||
<td>
|
||||
<AgentStatus agent={a} />
|
||||
</td>
|
||||
<td style={{ fontWeight: 600 }}>{a.name}</td>
|
||||
<td style={{ fontWeight: 600 }}>
|
||||
<span className="row" style={{ gap: 8 }}>
|
||||
{a.name}
|
||||
<NodeTag info={infos.get(a.id)} />
|
||||
</span>
|
||||
</td>
|
||||
<td className="mono muted">{a.public_host || '—'}</td>
|
||||
<td className="muted">{a.agent_version ?? '—'}</td>
|
||||
<td>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { api } from '@shared/api/client'
|
||||
import type { Page, ProbeDto, ProbeIdentityDto } from '@shared/api/types'
|
||||
import { fmtAgo, fmtTime } from '@shared/format'
|
||||
import { useQuery } from '@shared/hooks'
|
||||
import { NodeTag, useNodeInfos } from '@shared/nodes'
|
||||
import { Chip, Confirm, CopyButton, Dialog, downloadText, Empty, ErrorNotice, Field, Spinner, useAction } from '@shared/ui'
|
||||
|
||||
/** probe.json 只在创建和轮换密钥时返回一次:这里让管理员马上下载或复制。 */
|
||||
@@ -84,6 +85,7 @@ type Pending = { kind: 'rotate' | 'revoke' | 'delete'; probe: ProbeDto }
|
||||
|
||||
export function Probes() {
|
||||
const probes = useQuery(() => api.get<Page<ProbeDto>>('/api/admin/probes'), [], 8000)
|
||||
const infos = useNodeInfos()
|
||||
const { run } = useAction()
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [renaming, setRenaming] = useState<ProbeDto>()
|
||||
@@ -123,6 +125,7 @@ export function Probes() {
|
||||
<div key={p.id} className="card">
|
||||
<div className="row wrap">
|
||||
<span style={{ fontSize: 18, fontWeight: 700 }}>{p.name}</span>
|
||||
<NodeTag info={infos.get(p.id)} />
|
||||
{p.revoked ? <Chip tone="bad">已吊销</Chip> : <Chip tone={p.connections.length > 0 ? 'ok' : ''}>{p.connections.length > 0 ? `连着 ${p.connections.length} 个 Agent` : '没有连接'}</Chip>}
|
||||
<span className="mono faint">{p.id}</span>
|
||||
<span className="right row wrap">
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Download, KeyRound, Plus, Trash2 } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { api } from '@shared/api/client'
|
||||
import type { ApiTokenDto, CreatedApiTokenDto, EventDto, Page, RawChunkMetaDto, RecomputeDto, SettingDto, UserDto } from '@shared/api/types'
|
||||
import type { AgentDto, ApiTokenDto, BuildInfoDto, CreatedApiTokenDto, EventDto, Page, ProbeDto, RawChunkMetaDto, RecomputeDto, SettingDto, UserDto } from '@shared/api/types'
|
||||
import { fmtBuiltAt, frontendBuild } from '@shared/buildInfo'
|
||||
import { fmtNum, fmtTime } from '@shared/format'
|
||||
import { translateSetting } from '@shared/i18n'
|
||||
import { useQuery } from '@shared/hooks'
|
||||
import { Chip, Confirm, CopyButton, Dialog, Empty, ErrorNotice, Field, Segmented, Spinner, useAction } from '@shared/ui'
|
||||
|
||||
type Tab = 'settings' | 'tokens' | 'users' | 'events' | 'raw' | 'analysis'
|
||||
type Tab = 'settings' | 'versions' | 'tokens' | 'users' | 'events' | 'raw' | 'analysis'
|
||||
|
||||
function Settings() {
|
||||
const settings = useQuery(() => api.get<Page<SettingDto>>('/api/admin/settings'), [])
|
||||
@@ -76,6 +77,77 @@ function Settings() {
|
||||
)
|
||||
}
|
||||
|
||||
/** 一行:名字、类型、版本、tag、提交、构建时间、系统。没有的写「—」。 */
|
||||
function BuildRow({ name, kind, version, tag, commit, builtAt, platform }: { name: string; kind: string; version?: string | null; tag?: string | null; commit?: string | null; builtAt?: string | null; platform?: string }) {
|
||||
const dash = <span className="faint">—</span>
|
||||
return (
|
||||
<tr>
|
||||
<td style={{ fontWeight: 600 }}>{name}</td>
|
||||
<td>
|
||||
<Chip>{kind}</Chip>
|
||||
</td>
|
||||
<td className="mono">{version || dash}</td>
|
||||
<td className="mono">{tag || dash}</td>
|
||||
<td className="mono">{commit || dash}</td>
|
||||
<td className="muted">{fmtBuiltAt(builtAt) || dash}</td>
|
||||
<td className="muted">{platform || dash}</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
/** 每个程序的版本:后端、前端、每个 Agent 和 Probe 自己上报的(它们没升级就能看出来)。 */
|
||||
function Versions() {
|
||||
const info = useQuery(() => api.get<BuildInfoDto>('/api/admin/build-info'), [], 15_000)
|
||||
const agents = useQuery(() => api.get<Page<AgentDto>>('/api/admin/agents'), [], 30_000)
|
||||
const probes = useQuery(() => api.get<Page<ProbeDto>>('/api/admin/probes'), [], 30_000)
|
||||
const names = new Map<string, string>([...(agents.data?.items ?? []).map((a) => [a.id, a.name] as const), ...(probes.data?.items ?? []).map((p) => [p.id, p.name] as const)])
|
||||
const back = info.data?.backend
|
||||
const nodes = info.data?.nodes ?? []
|
||||
// 以后端的版本为准,节点的版本不一样就标出来(升级了一半的时候一眼能看到)
|
||||
const mismatch = (v?: string | null) => Boolean(v && back && v !== back.version)
|
||||
return (
|
||||
<div className="card">
|
||||
<ErrorNotice error={info.error} />
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>类型</th>
|
||||
<th>版本</th>
|
||||
<th>tag</th>
|
||||
<th>提交</th>
|
||||
<th>构建时间</th>
|
||||
<th>系统</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{back ? <BuildRow name="后端" kind="backend" version={back.version} tag={back.tag} commit={back.commit} builtAt={back.built_at} /> : null}
|
||||
<BuildRow name="前端" kind="web" version={frontendBuild.version} tag={frontendBuild.tag} commit={frontendBuild.commit} builtAt={frontendBuild.builtAt} />
|
||||
{nodes.map((n) => (
|
||||
<tr key={n.id}>
|
||||
<td style={{ fontWeight: 600 }}>{names.get(n.id) ?? n.id}</td>
|
||||
<td>
|
||||
<Chip>{n.kind}</Chip>
|
||||
</td>
|
||||
<td className="mono">
|
||||
{n.version ? mismatch(n.version) ? <Chip tone="warn">{n.version}</Chip> : n.version : <span className="faint">—</span>}
|
||||
</td>
|
||||
<td className="mono">{n.tag || <span className="faint">—</span>}</td>
|
||||
<td className="mono">{n.commit || <span className="faint">—</span>}</td>
|
||||
<td className="muted">{fmtBuiltAt(n.built_at) || <span className="faint">—</span>}</td>
|
||||
<td className="muted">{[n.os_name, n.arch].filter(Boolean).join(' / ') || <span className="faint">—</span>}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{nodes.length === 0 && !info.loading ? <Empty>还没有节点上报过版本</Empty> : null}
|
||||
<p className="muted" style={{ marginTop: 12 }}>
|
||||
版本、tag 和构建时间是各个程序自己上报的。设置里打开「site.show_build」之后,展示页底部和节点卡片上才会给访客显示(提交号只在这里显示)。
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Tokens() {
|
||||
const tokens = useQuery(() => api.get<Page<ApiTokenDto>>('/api/admin/api-tokens'), [])
|
||||
const { run } = useAction()
|
||||
@@ -418,6 +490,7 @@ export function System() {
|
||||
<Segmented
|
||||
options={[
|
||||
{ value: 'settings' as const, label: '设置' },
|
||||
{ value: 'versions' as const, label: '版本' },
|
||||
{ value: 'tokens' as const, label: 'API 令牌' },
|
||||
{ value: 'users' as const, label: '用户' },
|
||||
{ value: 'events' as const, label: '事件' },
|
||||
@@ -427,7 +500,7 @@ export function System() {
|
||||
value={tab}
|
||||
onChange={setTab}
|
||||
/>
|
||||
{tab === 'settings' ? <Settings /> : tab === 'tokens' ? <Tokens /> : tab === 'users' ? <Users /> : tab === 'events' ? <Events /> : tab === 'raw' ? <Raw /> : <Analysis />}
|
||||
{tab === 'settings' ? <Settings /> : tab === 'versions' ? <Versions /> : tab === 'tokens' ? <Tokens /> : tab === 'users' ? <Users /> : tab === 'events' ? <Events /> : tab === 'raw' ? <Raw /> : <Analysis />}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { buildDefines } from '../build-info.mjs'
|
||||
|
||||
// 开发时把 /api 转发给后端,地址取自环境变量 BACKEND_URL,或 web/.env.local 里的 BACKEND_URL=(见 web/.env.example):
|
||||
// BACKEND_URL=https://mo.oeo.one npm run dev:display
|
||||
@@ -9,6 +10,7 @@ export default defineConfig(({ mode }) => {
|
||||
const backend = (process.env.BACKEND_URL ?? env.BACKEND_URL ?? 'http://localhost:29181').replace(/\/+$/, '')
|
||||
return {
|
||||
base: '/admin/',
|
||||
define: buildDefines(mode === 'development'),
|
||||
plugins: [react()],
|
||||
resolve: { alias: { '@shared': fileURLToPath(new URL('../shared', import.meta.url)) } },
|
||||
server: {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export function buildDefines(dev: boolean): Record<string, string>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { execSync } from 'node:child_process'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
/**
|
||||
* 前端自己的构建信息:版本取自 web/package.json,tag 和提交取自 git(环境变量 MONITOR_TAG / MONITOR_COMMIT 优先,CI 里没有 git 历史时用),
|
||||
* 构建时间是这次构建的时刻。开发服务器里 tag 是「dev」,免得每次刷新都变。
|
||||
*/
|
||||
function git(args) {
|
||||
try {
|
||||
return execSync(`git ${args}`, { cwd: fileURLToPath(new URL('..', import.meta.url)), stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function buildDefines(dev) {
|
||||
const version = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version
|
||||
return {
|
||||
__APP_VERSION__: JSON.stringify(version),
|
||||
__APP_TAG__: JSON.stringify(dev ? 'dev' : (process.env.MONITOR_TAG || git('describe --tags --always --dirty'))),
|
||||
__APP_COMMIT__: JSON.stringify(dev ? '' : (process.env.MONITOR_COMMIT || git('rev-parse --short=10 HEAD'))),
|
||||
__APP_BUILT_AT__: JSON.stringify(dev ? '' : (process.env.MONITOR_BUILT_AT || new Date().toISOString())),
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BuildFooter } from './BuildFooter'
|
||||
import { NavLink, Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { SourceButton, ThemeToggle } from '@shared/ui'
|
||||
import { Compare } from './pages/Compare'
|
||||
@@ -9,9 +10,6 @@ export function App() {
|
||||
<>
|
||||
<header className="topbar">
|
||||
<div className="inner">
|
||||
<NavLink to="/" className="brand">
|
||||
MyMonitor
|
||||
</NavLink>
|
||||
<nav className="topnav">
|
||||
<NavLink to="/" end className={({ isActive }) => (isActive ? 'active' : '')}>
|
||||
总览
|
||||
@@ -31,6 +29,10 @@ export function App() {
|
||||
<Route path="/compare" element={<Compare />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
<footer className="site-foot">
|
||||
国家和运营商由 <a href="https://ip.007995.xyz/" target="_blank" rel="noreferrer">ip.007995.xyz</a> 查询
|
||||
<BuildFooter />
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { api } from '@shared/api/client'
|
||||
import type { AboutDto, BuildDto } from '@shared/api/types'
|
||||
import { fmtBuiltAt, frontendBuild } from '@shared/buildInfo'
|
||||
import { useQuery } from '@shared/hooks'
|
||||
|
||||
/** 「0.1.1 · v0.1.1-5-gabc123 · 2026-10-06 20:30」:版本、tag(和版本一样就不重复写)、构建时间。 */
|
||||
function buildText(b: { version: string; tag?: string | null; built_at?: string | null }): string {
|
||||
const parts = [b.version]
|
||||
if (b.tag && b.tag.replace(/^v/, '') !== b.version) parts.push(b.tag)
|
||||
const at = fmtBuiltAt(b.built_at)
|
||||
if (at) parts.push(at)
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面底部的构建信息:后端和前端的版本、tag、构建时间。要管理员在设置里打开「site.show_build」才显示;
|
||||
* 关着的时候(或者后端太旧没有这个接口)什么也不画,前端自己的信息也不单独露出来。
|
||||
*/
|
||||
export function BuildFooter() {
|
||||
const about = useQuery(() => api.get<AboutDto>('/api/public/about'), [], 5 * 60_000)
|
||||
if (!about.data?.show_build || !about.data.backend) return null
|
||||
const backend: BuildDto = about.data.backend
|
||||
return (
|
||||
<div className="build-info">
|
||||
<span>后端 {buildText(backend)}</span>
|
||||
<span>前端 {buildText({ version: frontendBuild.version, tag: frontendBuild.tag, built_at: frontendBuild.builtAt })}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useRef, useState, type MouseEvent } from 'react'
|
||||
import { useRef, useState, type MouseEvent } from 'react'
|
||||
import { api } from '@shared/api/client'
|
||||
import type { SeriesDto } from '@shared/api/types'
|
||||
import { fmtMs, fmtPct } from '@shared/format'
|
||||
@@ -30,9 +30,7 @@ interface StripProps {
|
||||
/** 每 5 分钟一格:这一格的丢包(重传)率、延迟。 */
|
||||
t: number[]
|
||||
badSeries: (number | null)[]
|
||||
rttSeries: (number | null)[]
|
||||
ready: boolean
|
||||
loading: boolean
|
||||
/** 提示里这一格的「丢包」叫什么。 */
|
||||
tipBad: string
|
||||
/** 没有数据时占位的格数,和每格长度的说明(「每 5 分钟」)。 */
|
||||
@@ -41,42 +39,13 @@ interface StripProps {
|
||||
hoursText: string
|
||||
/** 悬停的那一格(UDP 和 TCP 共用);`null` = 没有。 */
|
||||
hover: number | null
|
||||
onHover: (i: number | null) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 一排小格(每 5 分钟一格,按这 5 分钟的丢包率着色:绿 / 黄 / 红 / 灰 = 没有数据)
|
||||
* 加一条延迟的小走势(只标纵轴最大值),尽量轻。
|
||||
* 尽量轻。
|
||||
*/
|
||||
function Strip({ rttLabel, rtt, badLabel, bad, badHint, t, badSeries, rttSeries, ready, loading, cells, stepText, hoursText, hover, onHover }: StripProps) {
|
||||
const count = ready ? badSeries.length : 0
|
||||
const onSparkMove = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (count < 2) return
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
onHover(Math.min(count - 1, Math.max(0, Math.round(((e.clientX - rect.left) / rect.width) * (count - 1)))))
|
||||
}
|
||||
// 纵轴贴着数据走:从最小值到最大值(上下各留一点),而不是从 0 开始——延迟本来就在一个窄范围里波动,从 0 画会成一条平线
|
||||
const spark = useMemo(() => {
|
||||
const points = rttSeries.map((v, i) => ({ v, i })).filter((p): p is { v: number; i: number } => p.v !== null && p.v !== undefined)
|
||||
if (points.length < 2) return null
|
||||
const lo = Math.min(...points.map((p) => p.v))
|
||||
const hi = Math.max(...points.map((p) => p.v))
|
||||
const mean = points.reduce((sum, p) => sum + p.v, 0) / points.length
|
||||
const pad = Math.max((hi - lo) * 0.15, mean * 0.03, 1)
|
||||
const top = hi + pad
|
||||
const bottom = Math.max(0, lo - pad)
|
||||
const n = rttSeries.length - 1
|
||||
const W = 300
|
||||
const H = 30
|
||||
const x = (i: number) => (i / n) * W
|
||||
const y = (v: number) => H - ((v - bottom) / (top - bottom)) * H
|
||||
const line = points.map((p, k) => `${k === 0 ? 'M' : 'L'}${x(p.i).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ')
|
||||
const area = `${line} L${x(points[points.length - 1].i).toFixed(1)},${H} L${x(points[0].i).toFixed(1)},${H} Z`
|
||||
const fmt = (v: number) => (v >= 100 ? String(Math.round(v)) : v.toFixed(1))
|
||||
const dots = rttSeries.map((v) => (v === null || v === undefined ? null : y(v) / H))
|
||||
return { line, area, hi: fmt(hi), lo: fmt(lo), W, H, dots }
|
||||
}, [rttSeries])
|
||||
|
||||
function Strip({ rttLabel, rtt, badLabel, bad, badHint, t, badSeries, ready, cells, stepText, hoursText, hover }: StripProps) {
|
||||
return (
|
||||
<div className="udp-strip">
|
||||
<div className="udp-head">
|
||||
@@ -89,29 +58,9 @@ function Strip({ rttLabel, rtt, badLabel, bad, badHint, t, badSeries, rttSeries,
|
||||
</div>
|
||||
<div className="bars" role="img" aria-label={`近 ${hoursText}每 ${stepText}的${badLabel}情况`}>
|
||||
{(ready ? badSeries : Array.from({ length: cells }, () => null)).map((v, i) => (
|
||||
<span key={t[i] ?? i} className={`bar ${ready ? levelOf(v) : 'none'}${hover === i ? ' on' : ''}`} onMouseEnter={() => ready && onHover(i)} />
|
||||
<span key={t[i] ?? i} className={`bar ${ready ? levelOf(v) : 'none'}${hover === i ? ' on' : ''}`} />
|
||||
))}
|
||||
</div>
|
||||
<div className="spark" onMouseMove={onSparkMove}>
|
||||
{spark ? (
|
||||
<>
|
||||
<svg viewBox={`0 0 ${spark.W} ${spark.H}`} preserveAspectRatio="none" aria-hidden="true">
|
||||
<path className="area" d={spark.area} />
|
||||
<path className="line" d={spark.line} vectorEffect="non-scaling-stroke" />
|
||||
</svg>
|
||||
{hover !== null && spark.dots[hover] !== null && spark.dots[hover] !== undefined ? (
|
||||
<>
|
||||
<i className="guide" style={{ left: `${(hover / (count - 1)) * 100}%` }} />
|
||||
<i className="dot" style={{ left: `${(hover / (count - 1)) * 100}%`, top: `${(spark.dots[hover] as number) * 100}%` }} />
|
||||
</>
|
||||
) : null}
|
||||
<span className="ymax num">{spark.hi}</span>
|
||||
<span className="ymin num">{spark.lo}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="faint empty">{loading ? '加载中…' : '没有数据'}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -123,7 +72,7 @@ interface Props {
|
||||
m: Record<string, number | null | undefined>
|
||||
}
|
||||
|
||||
/** 一条线路近一段时间的 UDP 和 TCP:各是一排小格加一条延迟走势,共用一次请求。 */
|
||||
/** 一条线路近一段时间的 UDP 和 TCP:各是一排小格,共用一次请求。 */
|
||||
export function LineStrips({ lineId, range, m }: Props) {
|
||||
const info = OVERVIEW_RANGES.find((r) => r.value === range) ?? OVERVIEW_RANGES[0]
|
||||
const series = useQuery(
|
||||
@@ -139,10 +88,18 @@ export function LineStrips({ lineId, range, m }: Props) {
|
||||
const last = useRef<number | null>(null) // 提示收回去的动画期间,内容还要留着
|
||||
if (hover !== null) last.current = hover
|
||||
const shown = hover ?? last.current
|
||||
const common = { t, ready: Boolean(s), loading: series.loading, cells: Math.round((info.hours * 60) / info.stepMin), stepText: `${info.stepMin} 分钟`, hoursText: `${info.hours} 小时`, hover, onHover: setHover }
|
||||
// 整块线路区域都能触发:按鼠标横向位置对应到小格(UDP 和 TCP 的小格一样宽,量第一排就行),不用精确指到小格上
|
||||
const box = useRef<HTMLDivElement>(null)
|
||||
const onMove = (e: MouseEvent<HTMLDivElement>) => {
|
||||
const bars = box.current?.querySelector('.bars')
|
||||
if (!s || t.length < 1 || !bars) return
|
||||
const r = bars.getBoundingClientRect()
|
||||
setHover(Math.min(t.length - 1, Math.max(0, Math.floor(((e.clientX - r.left) / r.width) * t.length))))
|
||||
}
|
||||
const common = { t, ready: Boolean(s), cells: Math.round((info.hours * 60) / info.stepMin), stepText: `${info.stepMin} 分钟`, hoursText: `${info.hours} 小时`, hover }
|
||||
const at = shown !== null && t[shown] !== undefined ? shown : null
|
||||
return (
|
||||
<div className="strips" onMouseLeave={() => setHover(null)}>
|
||||
<div className="strips" ref={box} onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
|
||||
<Strip
|
||||
{...common}
|
||||
rttLabel="UDP 延迟 p50"
|
||||
@@ -151,10 +108,9 @@ export function LineStrips({ lineId, range, m }: Props) {
|
||||
bad={m['loss.total']}
|
||||
badHint={`上行丢包 ${fmtPct(m['loss.up'])} 下行丢包 ${fmtPct(m['loss.down'])}`}
|
||||
badSeries={col('loss.total')}
|
||||
rttSeries={col('rtt.p50')}
|
||||
tipBad="丢包"
|
||||
/>
|
||||
<Strip {...common} rttLabel="TCP RTT p50" rtt={m['tcp.rtt.p50']} badLabel="重传" bad={m['tcp.retrans']} badSeries={col('tcp.retrans')} rttSeries={col('tcp.rtt.p50')} tipBad="重传" />
|
||||
<Strip {...common} rttLabel="TCP RTT p50" rtt={m['tcp.rtt.p50']} badLabel="重传" bad={m['tcp.retrans']} badSeries={col('tcp.retrans')} tipBad="重传" />
|
||||
<div className={`pop num${hover !== null && at !== null ? ' open' : ''}`} role="tooltip" aria-hidden={hover === null}>
|
||||
{at !== null ? (
|
||||
<>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
+257
-68
@@ -862,54 +862,6 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.spark {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.spark svg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.spark .area {
|
||||
fill: color-mix(in srgb, var(--app-accent) 12%, transparent);
|
||||
}
|
||||
|
||||
.spark .line {
|
||||
fill: none;
|
||||
stroke: var(--app-accent);
|
||||
stroke-width: 1.2;
|
||||
}
|
||||
|
||||
.spark .ymax,
|
||||
.spark .ymin {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
font-size: 9px;
|
||||
line-height: 1;
|
||||
color: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
.spark .ymax {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.spark .ymin {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.spark .empty {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* UDP 和 TCP 两段之间留一点空 */
|
||||
.strips {
|
||||
display: flex;
|
||||
@@ -923,26 +875,6 @@
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.spark .guide {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: var(--app-ink-faint);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spark .dot {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin: -3.5px 0 0 -3.5px;
|
||||
border-radius: 50%;
|
||||
background: var(--app-accent);
|
||||
box-shadow: 0 0 0 2px var(--app-card);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.strips {
|
||||
position: relative;
|
||||
}
|
||||
@@ -1038,3 +970,260 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.site-foot {
|
||||
max-width: 900px;
|
||||
margin: 48px auto 24px;
|
||||
padding: 0 16px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
color: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
.site-foot a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Agent 卡片:国旗开头,名字下面是「运营商 / 系统」两行(左边名字右边内容,和下面的流量、网速一个风格) */
|
||||
/* 亚克力:整张卡片半透明 + 背景模糊,细亮边,大一点的投影;标题栏是稍深的一条,底下垫放大模糊的国旗 */
|
||||
/* 背景图固定在最底下,自己模糊一点(边缘放大一点,免得模糊后露出白边);上面盖一层页面底色,亮色盖得浅一点,暗色盖得深 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: -24px;
|
||||
z-index: -1;
|
||||
background-image:
|
||||
linear-gradient(color-mix(in srgb, var(--app-bg) var(--bg-veil), transparent), color-mix(in srgb, var(--app-bg) var(--bg-veil), transparent)),
|
||||
url('./assets/bg.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
filter: blur(var(--bg-blur));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-veil: 50%;
|
||||
--bg-blur: 10px;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg-veil: 82%;
|
||||
}
|
||||
|
||||
.agent-card {
|
||||
background: color-mix(in srgb, var(--app-card) 76%, transparent);
|
||||
backdrop-filter: blur(28px) saturate(1.6);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-ink) 9%, transparent),
|
||||
inset 0 1px 0 color-mix(in srgb, var(--app-card) 70%, transparent),
|
||||
0 14px 32px -12px color-mix(in srgb, var(--app-shadow) 45%, transparent);
|
||||
}
|
||||
|
||||
.agent-head {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
margin: -20px -20px 0;
|
||||
padding: 16px 20px;
|
||||
border-radius: var(--r-card) var(--r-card) 0 0;
|
||||
background: color-mix(in srgb, var(--app-ink) 5%, transparent);
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--app-ink) 8%, transparent);
|
||||
}
|
||||
|
||||
.agent-head .flag {
|
||||
flex: none;
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
border-radius: 3px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 0 0 1px var(--app-outline-soft);
|
||||
}
|
||||
|
||||
.agent-head .flag.ph {
|
||||
display: inline-block;
|
||||
background: var(--app-card-inset);
|
||||
}
|
||||
|
||||
.facts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.fact {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.fact .k {
|
||||
color: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
.fact .v {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--app-ink);
|
||||
}
|
||||
|
||||
.fact .v .isp {
|
||||
flex: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.fact .v .isp rect {
|
||||
fill: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
.fact .v .isp text {
|
||||
fill: var(--isp-ink);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fact .v .isp-broadnet rect {
|
||||
fill: var(--isp-broadnet);
|
||||
}
|
||||
|
||||
/* 线路行:国旗在 Probe 名字前面,运营商图标在后面 */
|
||||
.line-name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.line-name .flag {
|
||||
flex: none;
|
||||
width: 18px;
|
||||
height: 13.5px;
|
||||
border-radius: 2px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 0 0 1px var(--app-outline-soft);
|
||||
}
|
||||
|
||||
.line-top .isp {
|
||||
flex: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.line-top .isp rect {
|
||||
fill: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
.line-top .isp text {
|
||||
fill: var(--isp-ink);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.line-top .isp-broadnet rect {
|
||||
fill: var(--isp-broadnet);
|
||||
}
|
||||
|
||||
/* 亚克力卡片里的线路块:不用实色,只叠一层淡淡的墨色和细描边,透出卡片本身 */
|
||||
.agent-card .line-link {
|
||||
background: color-mix(in srgb, var(--app-ink) 4%, transparent);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--app-ink) 7%, transparent);
|
||||
}
|
||||
|
||||
.agent-card .line-link:hover {
|
||||
background: color-mix(in srgb, var(--app-ink) 7%, transparent);
|
||||
}
|
||||
|
||||
|
||||
/* 线路详情、对比等页面的卡片和指标块:同样的亚克力,一层压一层 */
|
||||
.card:not(.agent-card),
|
||||
.tiles > .stat,
|
||||
.tiles > .tile {
|
||||
background: color-mix(in srgb, var(--app-card) 76%, transparent);
|
||||
backdrop-filter: blur(28px) saturate(1.6);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-ink) 9%, transparent),
|
||||
inset 0 1px 0 color-mix(in srgb, var(--app-card) 70%, transparent),
|
||||
0 14px 32px -12px color-mix(in srgb, var(--app-shadow) 45%, transparent);
|
||||
}
|
||||
|
||||
/* 小的指标块浮得低一点:阴影收小 */
|
||||
.tiles > .stat {
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-ink) 8%, transparent),
|
||||
inset 0 1px 0 color-mix(in srgb, var(--app-card) 70%, transparent),
|
||||
0 6px 14px -8px color-mix(in srgb, var(--app-shadow) 40%, transparent);
|
||||
}
|
||||
|
||||
.topbar {
|
||||
background: color-mix(in srgb, var(--app-card) 62%, transparent);
|
||||
backdrop-filter: blur(20px) saturate(1.5);
|
||||
border-bottom-color: color-mix(in srgb, var(--app-ink) 7%, transparent);
|
||||
box-shadow: 0 4px 14px -8px color-mix(in srgb, var(--app-shadow) 28%, transparent);
|
||||
}
|
||||
|
||||
/* 时间轴:同样的亚克力。有数据的段是半透明的亮底,没数据的段是淡淡的斜纹,整条有细描边和投影 */
|
||||
.minimap .track {
|
||||
background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--app-card) 55%, var(--app-idle)) 0 5px, color-mix(in srgb, var(--app-card) 70%, var(--app-idle)) 5px 10px);
|
||||
backdrop-filter: blur(20px) saturate(1.6);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-ink) 9%, transparent),
|
||||
0 10px 24px -12px color-mix(in srgb, var(--app-shadow) 45%, transparent);
|
||||
}
|
||||
|
||||
.minimap svg rect.has {
|
||||
fill: color-mix(in srgb, var(--app-card) 80%, transparent);
|
||||
}
|
||||
|
||||
/* 直接落在背景图上的字(页面标题、分组名、说明):加一圈和页面底色一样的柔光,免得被图案吃掉 */
|
||||
body {
|
||||
text-shadow: 0 0 8px color-mix(in srgb, var(--app-bg) 90%, transparent), 0 0 2px color-mix(in srgb, var(--app-bg) 70%, transparent);
|
||||
}
|
||||
|
||||
.card,
|
||||
.pop,
|
||||
.tiles > .stat,
|
||||
.tiles > .tile,
|
||||
.topbar,
|
||||
button,
|
||||
input {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/* 时间轴整条做成圆角:轨道、里面的图、选区用同一个半径 */
|
||||
.minimap .track,
|
||||
.minimap svg,
|
||||
.minimap .sel {
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
/* 版本、tag:等宽的小字 */
|
||||
.fact .v.mono-tag,
|
||||
.probe-tag {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.probe-tag {
|
||||
padding: 0 6px;
|
||||
border-radius: 6px;
|
||||
background: color-mix(in srgb, var(--app-ink) 6%, transparent);
|
||||
color: var(--app-ink-muted);
|
||||
}
|
||||
|
||||
.build-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 4px 16px;
|
||||
margin-top: 4px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { api } from '@shared/api/client'
|
||||
import type { LineDto, TestDto } from '@shared/api/types'
|
||||
import { TimeChart, type ChartSeries } from '@shared/charts/TimeChart'
|
||||
import { fmtAgo, fmtMbps, fmtMs, fmtNum, fmtPct, fmtTime } from '@shared/format'
|
||||
import { NodeTag, useNodeInfos } from '@shared/nodes'
|
||||
import { fmtBytes, useHosts } from '../live'
|
||||
import { useQuery } from '@shared/hooks'
|
||||
import { Chip, Dot, LoadingBar, Segmented, Spinner, Stat } from '@shared/ui'
|
||||
@@ -146,6 +147,7 @@ export function Line() {
|
||||
const [params, setParams] = useSearchParams()
|
||||
|
||||
const line = useQuery(() => api.get<LineDto>(`/api/public/lines/${encodeURIComponent(id)}`), [id], 15_000)
|
||||
const infos = useNodeInfos()
|
||||
const ready = Boolean(line.data)
|
||||
const sinceMs = line.data?.data_since ? Date.parse(line.data.data_since) : null
|
||||
const nowMs = useMemo(() => Date.now(), [line.data])
|
||||
@@ -258,6 +260,14 @@ export function Line() {
|
||||
<div className="row wrap" style={{ marginTop: 12 }}>
|
||||
<Dot tone={line.data.online && line.data.probe_connected ? 'ok' : line.data.online ? 'warn' : 'bad'} />
|
||||
<span className="muted">{line.data.online ? (line.data.probe_connected ? '在线' : 'Agent 在线,Probe 未连接') : 'Agent 离线'}</span>
|
||||
<span className="row" style={{ gap: 6 }}>
|
||||
<span className="faint">Agent</span>
|
||||
<NodeTag info={infos.get(line.data.agent_id)} />
|
||||
</span>
|
||||
<span className="row" style={{ gap: 6 }}>
|
||||
<span className="faint">Probe</span>
|
||||
<NodeTag info={infos.get(line.data.probe_id)} />
|
||||
</span>
|
||||
{line.data.labels.map((x) => (
|
||||
<Chip key={x}>{x}</Chip>
|
||||
))}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Link, useSearchParams } from 'react-router-dom'
|
||||
import { api } from '@shared/api/client'
|
||||
import type { OverviewDto, OverviewLineDto } from '@shared/api/types'
|
||||
import type { NodeInfoDto, OverviewDto, OverviewLineDto } from '@shared/api/types'
|
||||
import { fmtAgo } from '@shared/format'
|
||||
import { useQuery } from '@shared/hooks'
|
||||
import { Chip, Dot, Empty, Segmented, Spinner } from '@shared/ui'
|
||||
import { fmtBytes, fmtSpeed, fmtUptime, pct, useHosts, type HostItem } from '../live'
|
||||
import { Flag, IspIcon, useNodeInfos } from '@shared/nodes'
|
||||
import { OsIcon } from '@shared/osIcon'
|
||||
import { PageError } from '../lib'
|
||||
import { LineStrips, OVERVIEW_RANGES, type OverviewRange } from '../LineStrips'
|
||||
|
||||
@@ -36,17 +38,49 @@ function Meter({ label, value, detail }: { label: string; value: number | null;
|
||||
)
|
||||
}
|
||||
|
||||
function Card({ agent, host, range }: { agent: AgentCard; host: HostItem | undefined; range: OverviewRange }) {
|
||||
function Card({ agent, host, range, infos }: { agent: AgentCard; host: HostItem | undefined; range: OverviewRange; infos: Map<string, NodeInfoDto> }) {
|
||||
const h = host?.host ?? null
|
||||
const stale = !agent.online || !host || host.stale
|
||||
const info = infos.get(agent.id)
|
||||
return (
|
||||
<div className={`card agent-card ${agent.online ? '' : 'offline'}`}>
|
||||
<div className="agent-head">
|
||||
<Dot tone={agent.online ? 'ok' : 'bad'} />
|
||||
{/* 有国旗就用国旗开头(在线、离线看右边的标签),没有再用状态点 */}
|
||||
{info?.country ? <Flag code={info.country} /> : <Dot tone={agent.online ? 'ok' : 'bad'} />}
|
||||
<span className="agent-name">{agent.name}</span>
|
||||
<Chip tone={agent.online ? 'ok' : 'bad'}>{agent.online ? '在线' : '离线'}</Chip>
|
||||
{!agent.online && host?.last_seen_at ? <span className="faint">{fmtAgo(host.last_seen_at)}</span> : null}
|
||||
</div>
|
||||
{info?.isp_name || info?.os_name || info?.tag || info?.version ? (
|
||||
<div className="facts">
|
||||
{info.isp_name ? (
|
||||
<div className="fact">
|
||||
<span className="k">运营商</span>
|
||||
<span className="v">
|
||||
{info.isp_key ? <IspIcon ispKey={info.isp_key} name={info.isp_name} /> : null}
|
||||
{info.isp_name}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{info.os_name ? (
|
||||
<div className="fact">
|
||||
<span className="k">系统</span>
|
||||
<span className="v">
|
||||
<OsIcon os={info.os} name={info.os_name} />
|
||||
{[info.os_name, info.arch].filter(Boolean).join(' / ')}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{info.tag || info.version ? (
|
||||
<div className="fact">
|
||||
<span className="k">版本</span>
|
||||
<span className="v mono-tag" title={info.version ? `版本 ${info.version}` : undefined}>
|
||||
{info.tag || info.version}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{agent.labels.length > 0 ? (
|
||||
<div className="line-meta">
|
||||
{agent.labels.map((x) => (
|
||||
@@ -61,7 +95,16 @@ function Card({ agent, host, range }: { agent: AgentCard; host: HostItem | undef
|
||||
return (
|
||||
<Link key={l.id} to={`/line/${encodeURIComponent(l.id)}`} className="line-link">
|
||||
<div className="line-top">
|
||||
<span className="line-name">→ {l.probe_name}</span>
|
||||
<span className="line-name">
|
||||
→ {infos.get(l.probe_id)?.country ? <Flag code={infos.get(l.probe_id)!.country!} /> : null}
|
||||
{l.probe_name}
|
||||
</span>
|
||||
{infos.get(l.probe_id)?.isp_key ? <IspIcon ispKey={infos.get(l.probe_id)!.isp_key!} name={infos.get(l.probe_id)!.isp_name} /> : null}
|
||||
{infos.get(l.probe_id)?.tag || infos.get(l.probe_id)?.version ? (
|
||||
<span className="probe-tag" title="Probe 版本">
|
||||
{infos.get(l.probe_id)?.tag || infos.get(l.probe_id)?.version}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="faint">近 {OVERVIEW_RANGES.find((r) => r.value === range)?.label ?? range}</span>
|
||||
{!l.probe_connected && l.online ? <Chip tone="warn">Probe 未连接</Chip> : null}
|
||||
</div>
|
||||
@@ -108,6 +151,7 @@ export function Overview() {
|
||||
const [probe, setProbe] = useState<string>('')
|
||||
const [sort, setSort] = useState<SortKey>('name')
|
||||
const { hosts, live } = useHosts()
|
||||
const infos = useNodeInfos()
|
||||
// 近多久的汇总和走势:3 / 6 / 12 小时,记在链接里(?range=6h)
|
||||
const [params, setParams] = useSearchParams()
|
||||
const range: OverviewRange = OVERVIEW_RANGES.find((r) => r.value === params.get('range'))?.value ?? '3h'
|
||||
@@ -187,7 +231,7 @@ export function Overview() {
|
||||
) : (
|
||||
<div className="cards">
|
||||
{cards.map((c) => (
|
||||
<Card key={c.id} agent={c} host={hosts.get(c.id)} range={range} />
|
||||
<Card key={c.id} agent={c} host={hosts.get(c.id)} range={range} infos={infos} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { buildDefines } from '../build-info.mjs'
|
||||
|
||||
// 开发时把 /api 转发给后端,地址取自环境变量 BACKEND_URL,或 web/.env.local 里的 BACKEND_URL=(见 web/.env.example):
|
||||
// BACKEND_URL=https://mo.oeo.one npm run dev:display
|
||||
@@ -9,6 +10,7 @@ export default defineConfig(({ mode }) => {
|
||||
const backend = (process.env.BACKEND_URL ?? env.BACKEND_URL ?? 'http://localhost:29181').replace(/\/+$/, '')
|
||||
return {
|
||||
base: '/',
|
||||
define: buildDefines(mode === 'development'),
|
||||
plugins: [react()],
|
||||
resolve: { alias: { '@shared': fileURLToPath(new URL('../shared', import.meta.url)) } },
|
||||
server: {
|
||||
|
||||
Generated
+30
-1
@@ -10,7 +10,11 @@
|
||||
"workspaces": [
|
||||
"display",
|
||||
"admin"
|
||||
]
|
||||
],
|
||||
"dependencies": {
|
||||
"flag-icons": "^7.5.0",
|
||||
"simple-icons": "^16.34.0"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "monitor-admin",
|
||||
@@ -998,6 +1002,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/flag-icons": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz",
|
||||
"integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "6.1.23",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz",
|
||||
@@ -2089,6 +2099,25 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-icons": {
|
||||
"version": "16.34.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.34.0.tgz",
|
||||
"integrity": "sha512-UTxKn0PVspk84B2hHF3Dqiqht1yDjJmDc/t4cO7PyCQxCMk6OjEu95pnpGjhLaGeQ4qBrGASbefTIe/+czrX7g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/simple-icons"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/simple-icons"
|
||||
}
|
||||
],
|
||||
"license": "CC0-1.0",
|
||||
"engines": {
|
||||
"node": ">=0.12.18"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
|
||||
+8
-1
@@ -2,7 +2,10 @@
|
||||
"name": "monitor-web",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"workspaces": ["display", "admin"],
|
||||
"workspaces": [
|
||||
"display",
|
||||
"admin"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "node dev.mjs",
|
||||
"dev:display": "npm run dev -w display",
|
||||
@@ -10,5 +13,9 @@
|
||||
"build": "npm run build -w display && npm run build -w admin",
|
||||
"gen:api": "npx -y openapi-typescript ../backend/openapi/v1.json -o shared/api/schema.ts",
|
||||
"lint:css": "npm run lint:css -w display && npm run lint:css -w admin"
|
||||
},
|
||||
"dependencies": {
|
||||
"flag-icons": "^7.5.0",
|
||||
"simple-icons": "^16.34.0"
|
||||
}
|
||||
}
|
||||
@@ -174,6 +174,40 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/public/nodes": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Display info of the visible agents and probes: system, architecture, country and ISP (never an address) */
|
||||
get: operations["nodes_list"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/public/about": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** The backend's version, tag and build time, if the site shows build info */
|
||||
get: operations["about_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/public/catalog/kinds": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -765,6 +799,23 @@ export interface paths {
|
||||
patch: operations["users_update"];
|
||||
trace?: never;
|
||||
};
|
||||
"/api/admin/build-info": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Version, tag, commit and build time of the backend and every agent and probe */
|
||||
get: operations["build_info_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/healthz": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -854,6 +905,10 @@ export interface paths {
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
AboutDto: {
|
||||
show_build: boolean;
|
||||
backend: null | components["schemas"]["BuildDto"];
|
||||
};
|
||||
AgentDto: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -915,6 +970,16 @@ export interface components {
|
||||
tcp_port: number | string;
|
||||
spki: string;
|
||||
};
|
||||
BuildDto: {
|
||||
version: string;
|
||||
tag: null | string;
|
||||
commit: null | string;
|
||||
built_at: null | string;
|
||||
};
|
||||
BuildInfoDto: {
|
||||
backend: components["schemas"]["BuildDto"];
|
||||
nodes: components["schemas"]["NodeBuildDto"][];
|
||||
};
|
||||
ChangePasswordRequest: {
|
||||
password: string;
|
||||
};
|
||||
@@ -1209,6 +1274,29 @@ export interface components {
|
||||
version: number | string;
|
||||
needs: string[];
|
||||
};
|
||||
NodeBuildDto: {
|
||||
id: string;
|
||||
kind: string;
|
||||
os: null | string;
|
||||
os_name: null | string;
|
||||
arch: null | string;
|
||||
version: null | string;
|
||||
tag: null | string;
|
||||
commit: null | string;
|
||||
built_at: null | string;
|
||||
};
|
||||
NodeInfoDto: {
|
||||
id: string;
|
||||
kind: string;
|
||||
os: null | string;
|
||||
os_name: null | string;
|
||||
arch: null | string;
|
||||
country: null | string;
|
||||
isp_key: null | string;
|
||||
isp_name: null | string;
|
||||
version?: null | string;
|
||||
tag?: null | string;
|
||||
};
|
||||
OverviewDto: {
|
||||
range: string;
|
||||
/** Format: date-time */
|
||||
@@ -1266,6 +1354,10 @@ export interface components {
|
||||
items: components["schemas"]["ModelDto"][];
|
||||
next_cursor: null | string;
|
||||
};
|
||||
PageDtoOfNodeInfoDto: {
|
||||
items: components["schemas"]["NodeInfoDto"][];
|
||||
next_cursor: null | string;
|
||||
};
|
||||
PageDtoOfPlanDto: {
|
||||
items: components["schemas"]["PlanDto"][];
|
||||
next_cursor: null | string;
|
||||
@@ -2133,6 +2225,64 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
nodes_list: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PageDtoOfNodeInfoDto"];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDto"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
about_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["AboutDto"];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDto"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
catalog_kinds: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -4370,6 +4520,44 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
build_info_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["BuildInfoDto"];
|
||||
};
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDto"];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDto"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
health_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
@@ -12,6 +12,11 @@ export type ProbeDto = Narrow<S['ProbeDto']>
|
||||
export type ProbeConnectionDto = Narrow<S['ProbeConnectionDto']>
|
||||
export type ProbeIdentityDto = Narrow<S['ProbeIdentityDto']>
|
||||
export type LineDto = Narrow<S['LineDto']>
|
||||
export type NodeInfoDto = Narrow<S['NodeInfoDto']>
|
||||
export type AboutDto = Narrow<S['AboutDto']>
|
||||
export type BuildDto = Narrow<S['BuildDto']>
|
||||
export type BuildInfoDto = Narrow<S['BuildInfoDto']>
|
||||
export type NodeBuildDto = Narrow<S['NodeBuildDto']>
|
||||
export type OverviewDto = Narrow<S['OverviewDto']>
|
||||
export type OverviewLineDto = Narrow<S['OverviewLineDto']>
|
||||
export type SeriesDto = Narrow<S['SeriesDto']>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
|
||||
|
||||
viewBox="0 0 315 308.5899"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="China Mobile.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
inkscape:zoom="0.33541667"
|
||||
inkscape:cx="894.40994"
|
||||
inkscape:cy="76.024845"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
id="g147"
|
||||
transform="translate(-4.2522286e-4,-4.801313e-4)"
|
||||
style="fill:none">
|
||||
<path
|
||||
id="path4"
|
||||
d="m 303.94434,190.02541 c -27.51878,-36.39255 -68.62753,-73.63677 -79.99218,-82.84938 -7.95801,-6.44699 -15.87023,-9.688803 -23.68172,-9.578911 -7.83896,0.09158 -15.39403,3.333391 -22.09744,9.157661 -1.33702,1.16303 -3.26929,2.87551 -5.64112,4.99093 -16.34643,13.77312 -66.83262,60.18416 -67.97733,61.26476 -3.83706,3.58065 -8.736412,5.93417 -14.240168,5.74186 -4.862719,-0.20147 -10.174163,-2.381 -15.63213,-7.42687 C 70.92761,167.85471 53.674573,154.31053 33.518558,131.65447 21.109924,117.70735 10.871657,105.28956 10.871657,105.28956 c 0,0 -2.2711,4.67956 -3.5440151,7.94885 -1.0622889,2.71982 -0.1465226,4.42315 0.3663065,5.16492 6.7400396,9.66133 37.5006296,46.46598 79.8914506,83.00505 7.774855,6.69426 15.870231,9.68881 23.690871,9.58808 7.8298,-0.10074 15.40319,-3.33339 22.08829,-9.15766 1.33701,-1.17219 3.26928,-2.88467 5.65027,-5.00009 16.33727,-13.76397 46.93302,-41.80473 61.42961,-55.25734 3.10444,-2.80224 5.39386,-4.9085 6.54772,-6.00742 3.82791,-3.58065 8.73641,-5.93417 14.23101,-5.7327 4.86272,0.19231 9.98185,2.59162 15.64129,7.42686 9.06609,7.73823 23.75498,21.3282 41.15454,39.671 12.80241,13.48924 22.57364,26.09018 22.67437,26.22755 0,0 2.38099,-4.84441 3.64475,-8.15948 1.04397,-2.72898 0.14652,-4.27663 -0.39378,-4.98177"
|
||||
style="fill:#8ec320;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.15766" />
|
||||
<path
|
||||
id="path5"
|
||||
style="fill:#0086d0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.15766"
|
||||
d="M 156.10156,0.03131407 C 137.03789,0.52973098 114.77341,7.1359501 93.236325,24.546939 64.362242,47.871483 27.182379,82.203304 17.749996,92.560611 c 0,0 7.032042,8.745959 18.076172,19.570309 2.133733,1.82237 4.385744,1.3185 6.253907,-0.65039 28.562721,-27.582854 59.508025,-55.725024 77.457035,-69.617185 30.29351,-23.416119 65.82473,-25.686148 100.45898,1.988282 12.89398,10.320675 24.34062,20.521892 41.0625,37.738281 21.38312,23.306232 35.33966,42.775662 50.60547,64.726562 0,0 -0.0636,-5.87932 -1.75781,-13.68164 -0.28389,-1.31871 -0.87122,-3.46178 -1.81446,-5.35742 -3.0678,-6.1631 -16.5557,-29.294843 -36.13476,-52.152346 C 233.13773,29.78552 208.6403,15.974983 192.92578,7.7422516 183.81906,2.9505093 170.92886,-0.35634353 156.10156,0.03131407 Z M 174.20312,44.796939 C 163.23454,44.687047 151.8957,47.985282 140.57226,56.250064 126.3779,66.625686 67.914597,120.91294 56.449215,133.8985 c 0,0 8.233,9.0834 20.56836,19.3125 3.19602,2.73814 7.079332,2.16165 10.421875,-1.19922 22.50952,-22.63771 68.45213,-63.150567 74.61523,-68.0957 17.32628,-14.707192 37.50228,-15.999325 55.98243,-2.16211 44.86334,33.60859 84.54217,86.49463 91.35546,96.35742 0,0 1.02645,-4.87142 1.63086,-9.23047 0.0366,-0.25642 0.87973,-4.18627 -1.37304,-7.49218 -42.07026,-60.76103 -86.85211,-98.241836 -104.04102,-107.591801 -9.8399,-5.370964 -20.43767,-8.890108 -31.40625,-9 z" />
|
||||
<path
|
||||
id="path7"
|
||||
style="fill:#0086d0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.15766"
|
||||
d="m 2.2636683,130.48249 c 0,0 -1.0264543,4.87143 -1.63085941,9.23046 -0.027473,0.24726 -0.86997028,4.17578 1.38281251,7.5 42.0794186,60.74272 86.8527046,98.24319 104.0507786,107.58399 19.67064,10.73277 42.37131,14.06667 65.02735,-2.46289 14.19436,-10.36646 72.65766,-64.66228 84.12304,-77.63867 0,0 -8.2336,-9.0939 -20.57812,-19.30469 -3.19602,-2.7473 -7.07872,-2.1603 -10.41211,1.19141 -22.50952,22.63771 -68.45275,63.15056 -74.625,68.0957 -17.31712,14.70718 -37.49252,16.00787 -55.972657,2.15234 C 48.765558,193.22156 9.0952779,140.35444 2.2636683,130.48249 Z m -2.26171879,31.79492 c 0,0 -0.11950648,5.6687 1.75781249,13.68164 0.3022026,1.30955 0.8601041,3.46178 1.8125,5.35742 3.067814,6.16309 16.557047,29.29484 36.126953,52.15234 38.819293,45.33039 63.316715,59.14032 79.031255,67.37305 20.81534,10.96171 61.40325,14.15734 99.6914,-16.80469 28.86492,-23.31538 66.06176,-57.6466 75.49414,-68.0039 0,0 -7.03204,-8.75513 -18.07617,-19.57032 -2.13374,-1.82236 -4.3955,-1.32765 -6.26367,0.6504 -28.56272,27.5737 -59.50803,55.72381 -77.45703,69.59765 -30.29352,23.43444 -65.8339,25.70642 -100.458987,-1.98633 C 78.775334,254.41315 67.319528,244.21119 50.597653,227.00397 29.214531,203.68858 15.258601,184.23746 0.00194951,162.27741 Z" />
|
||||
</g>
|
||||
<g
|
||||
id="g148"
|
||||
style="fill:#0086d0;fill-opacity:1"
|
||||
transform="translate(-4.2522286e-4,-4.801313e-4)">
|
||||
<path
|
||||
id="path9"
|
||||
style="fill:#0086d0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.15766"
|
||||
d="m 781.3418,15.791016 -0.51172,1.0625 c -7.17959,14.47825 -17.61958,27.327479 -30.95313,38.261718 -0.13736,0.119049 6.89468,5.513116 11.01563,8.123047 8.04042,-7.152128 14.6536,-14.2769 19.64453,-21.154297 h 39.14844 c -6.91404,8.992816 -13.68987,16.327529 -21.49219,21.4375 -5.16492,-4.587984 -9.61716,-8.250232 -16.26563,-13.460937 l -0.94335,-0.734375 -8.42383,9.140625 1.09961,0.861328 c 5.93416,4.871871 9.33907,7.892712 13.24023,11.601563 -10.23825,5.549537 -21.46383,9.935901 -34.27539,13.736328 0,0 8.3869,11.392965 8.625,11.310546 7.61001,-2.655719 23.06881,-9.523504 29.75391,-12.554687 -8.8463,17.463645 -28.24138,31.445845 -41.70313,38.808595 0,0 5.80411,6.22782 10.35547,9.83594 21.4747,-9.83532 36.20117,-28.92969 36.20117,-28.92969 h 29.63477 c -1.91396,10.20163 -10.81634,22.87679 -23.28907,30.52344 -4.9726,-4.92682 -9.79912,-9.77207 -15.6875,-15.04688 l -9.44921,8.22461 c 5.17407,4.78946 9.76168,9.71525 13.64453,13.78125 -18.91971,8.17779 -44.14063,9.33203 -44.14063,9.33203 l 5.23047,11.52149 c 0,0 74.07473,-1.87864 89.05664,-58.83008 l 0.10156,-12.363283 h -36.5664 c 2.37183,-3.168548 3.36133,-6.703125 3.36133,-6.703125 l -13.21485,-2.22461 c 2.51836,-1.382805 4.92578,-2.847883 7.2793,-4.322265 15.79695,-10.119207 26.91531,-22.079394 33.84766,-35.513672 l 0.16406,-0.310547 V 29.189453 h -46.51172 c 1.47438,-2.353517 2.83915,-4.798439 4.09375,-7.234375 l 0.67773,-1.328125 z M 436.50977,17.138672 V 44.335938 H 381.14258 V 120.4082 h 55.36719 v 41.29297 h 14.99218 V 120.4082 h 55.92578 V 44.335938 H 451.50195 V 17.138672 Z m 510.62304,0.300781 V 18.75 c 0.0275,9.661324 -8.3e-4,19.039133 -0.10156,28.160156 h -18.74414 v 13.625 h 18.54297 v 18.105469 c 0,27.546225 -4.72487,41.612575 -15.4668,57.958985 -6.39204,9.97268 -18.12305,16.75 -18.12305,16.75 l 10.7793,10.4668 c 0,0 36.7207,-19.23114 36.7207,-79.580082 l 0.28516,-23.701172 h 24.58789 c -0.27473,24.67072 -1.17188,48.948674 -2.55469,72.181644 -0.22894,7.27117 -3.32549,10.65039 -9.69921,10.65039 -5.36639,-0.10989 -16.17188,-0.13672 -16.17188,-0.13672 0.83334,4.34073 2.44531,14.45898 2.44531,14.45898 h 17.31641 c 12.24379,-0.20146 19.03914,-7.51923 20.1289,-21.84179 1.2546,-21.97837 2.21626,-51.501138 2.88477,-87.527348 L 1000,46.910156 h -38.70898 c 0.174,-9.276702 0.29284,-18.682851 0.32031,-28.115234 V 17.439453 Z M 541.71484,22.339844 V 158.01953 l 128.39844,-0.0742 V 22.339844 Z m 210.18555,0.667968 c 0,0 -12.52729,3.178015 -25.41211,4.917969 -16.5204,2.22531 -26.38281,3.195313 -26.38281,3.195313 L 701.57031,44.125 c 0,0 10.98938,-1.162815 19.47852,-2.673828 v 19.78125 H 698.3457 v 12.902344 h 20.74219 c -5.60448,17.298807 -14.11991,34.057594 -23.36914,47.281254 0,0 9.69675,10.47586 9.80664,10.34765 5.925,-8.35178 11.14608,-17.73735 15.52344,-27.90234 V 161.6543 H 734.3457 V 98.255859 c 4.92682,4.478091 9.4591,8.690861 13.72657,12.683591 l 0.9707,0.99024 9.41406,-10.52344 -0.9707,-0.87891 C 751.26828,95.041909 734.3457,81.296875 734.3457,81.296875 v -7.162109 h 19.95508 V 61.232422 H 734.3457 V 40.408203 c 14.10279,-2.316887 20.32032,-4.195312 20.32032,-4.195312 z m 114.58984,7.802735 v 13.296875 h 60.35743 V 30.810547 Z M 555.45898,35.197266 H 656.39453 V 145.0332 H 555.45898 Z M 565.45117,46.625 v 12.445312 h 33.63477 v 18.902344 h -30.87891 v 12.425782 h 30.87891 v 29.855472 h -35.88867 v 12.46289 h 85.25781 V 120.25391 H 612.38281 V 90.398438 h 30.90821 V 77.972656 H 612.38281 V 59.070312 h 33.78321 V 46.625 Z M 395.58008,57.632812 h 40.92969 v 49.423828 h -40.92969 z m 55.91797,0 h 41.43359 v 49.423828 h -41.43359 z m 409.0039,10.861329 v 13.341797 h 23.7168 c -1.41944,7.573379 -9.69655,32.703642 -20.92383,53.216792 -1.39197,2.53667 -3.33398,4.03711 -3.33398,4.03711 l 3.96484,9.56055 c 0,0 38.72781,-10.45791 50.36719,-12.20703 l 1.39258,3.49023 13.62695,-5.16601 C 925.01756,124.16302 918.4151,109.228 913.48828,98.541016 l -0.56836,-1.21875 -12.32617,5.531254 0.55859,1.24609 c 3.26012,7.26201 6.20931,13.85413 9.18555,21.33594 l -31.77734,7.50976 c 7.01476,-11.46539 15.77904,-37.29963 20.54101,-51.109372 h 33.49805 V 68.494141 Z m -233.88672,23.048828 -10.19335,7.693359 1.03515,1.072262 c 4.16673,4.36821 9.45038,10.78629 13.91016,16.77539 l 0.85156,1.08985 10.63281,-8.09571 L 642,108.99805 c -3.77295,-4.81693 -9.37852,-11.189522 -14.58008,-16.574222 z" />
|
||||
<path
|
||||
id="path18"
|
||||
style="fill:#0086d0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.15766"
|
||||
d="m 470.48633,187.40625 v 86.41211 h 11.19922 v -40.01953 c 1.9231,-2.72899 4.25907,-4.95345 6.89648,-6.54688 2.69235,-1.60259 5.54794,-2.4375 8.50586,-2.4375 3.49823,0 5.91673,0.9802 7.42773,2.97657 1.52933,2.00552 2.30665,5.19372 2.30665,9.40625 v 36.62109 h 11.18164 v -38.0957 c 0,-4.18505 -0.57708,-7.63713 -1.76758,-10.32032 -1.19965,-2.7015 -2.76439,-4.84493 -4.6875,-6.39257 -1.89564,-1.52933 -3.97465,-2.58297 -6.18164,-3.21485 -4.31326,-1.14471 -8.78321,-1.14419 -12.51953,-0.10937 -1.78574,0.45788 -3.52389,1.14456 -5.08985,1.96875 -1.57512,0.78755 -3.04066,1.82269 -4.35937,2.97656 -0.59524,0.57693 -1.20922,1.11643 -1.71289,1.69336 v -34.91797 z m 375.20703,0 v 86.41211 h 8.68164 l 1.53906,-5.0918 c 0.20147,0.19231 0.37484,0.34858 0.54883,0.56836 0.80588,0.94324 1.88564,1.81276 3.22266,2.72852 1.33702,0.8883 2.97693,1.64802 4.83593,2.28906 1.93226,0.60441 4.14867,0.95312 6.59375,0.95312 3.26929,0 6.49213,-0.65178 9.55079,-1.8789 3.08612,-1.26376 5.83312,-3.19563 8.23242,-5.75977 2.37184,-2.56414 4.32275,-5.84237 5.74218,-9.73437 1.43775,-3.8279 2.16993,-8.49876 2.16993,-13.7461 0,-3.79126 -0.40142,-7.1788 -1.15235,-10.13671 -0.75093,-2.93046 -1.76736,-5.49505 -3.05859,-7.65625 -1.30955,-2.15205 -2.82073,-4.00158 -4.5332,-5.4668 -1.68501,-1.44692 -3.47949,-2.61879 -5.34766,-3.52539 -1.7949,-0.90661 -3.70007,-1.54728 -5.5957,-1.87695 -1.84985,-0.37546 -3.61739,-0.55079 -5.23828,-0.55079 -3.49823,0 -6.67641,0.60504 -9.45118,1.80469 -2.02383,0.94324 -3.89251,2.10596 -5.56836,3.52539 v -32.85742 z m 83.82031,0 v 86.41211 h 11.19922 v -86.41211 z m -506.96875,4.3125 c -22.80256,0 -41.39258,18.57305 -41.39258,41.38477 0,22.82087 18.59002,41.42968 41.39258,41.42968 16.5021,0 31.41114,-9.79929 37.98633,-24.92773 l 0.6875,-1.52149 h -11.91406 l -0.29297,0.50391 c -5.54039,9.37744 -15.64245,15.19336 -26.4668,15.19336 -16.88671,0 -30.64062,-13.74523 -30.64062,-30.67773 0,-16.88672 13.75391,-30.63282 30.64062,-30.63282 10.77856,0 20.85172,5.72256 26.35547,15.01758 l 0.33984,0.53125 h 11.89454 L 460.47656,216.5 c -6.63013,-15.05518 -21.5028,-24.78125 -37.93164,-24.78125 z m 107.54883,1.72266 v 12.28125 h 11.19922 v -12.28125 z m 375.58203,0 v 12.28125 h 11.20899 v -12.28125 z m -216.13867,0.38476 v 79.99219 h 11.68555 v -38.72852 c 0,-7.4177 -0.21182,-15.5218 -0.57813,-24.17578 2.10626,5.58617 4.20394,10.83294 6.24609,15.75977 l 19.9004,47.14453 h 5.80468 l 19.88282,-47.14453 c 1.97805,-4.64294 4.07437,-9.91779 6.2539,-15.76953 -0.35715,8.76388 -0.54883,16.91437 -0.54883,24.18554 v 38.72852 h 11.64844 V 193.82617 H 754.3457 l -18.58984,44.66992 c -1.63922,3.8279 -3.67296,9.11179 -6.07227,15.79688 -2.17951,-6.06237 -4.19312,-11.35481 -6.04297,-15.78711 l -18.5996,-44.67969 z m 118.29883,20.9336 c -16.61199,0 -30.06446,13.46296 -30.06446,30.05664 0,16.64861 13.45247,30.10937 30.06446,30.10937 16.62114,0 30.08203,-13.46076 30.08203,-30.10937 0,-16.59368 -13.46089,-30.05664 -30.08203,-30.05664 z m -176.45899,0.17382 c -1.58427,0 -3.18794,0.0839 -4.7539,0.23047 -1.60259,0.17399 -3.13196,0.40166 -4.56055,0.68555 -1.41028,0.29305 -2.80131,0.64967 -4.07422,1.08008 -1.33702,0.43042 -2.47265,0.8883 -3.44336,1.38281 l -0.61328,0.27539 v 9.80859 l 1.49219,-0.55859 c 2.38099,-0.87913 4.7888,-1.6024 7.19726,-2.11523 2.39015,-0.49451 4.94559,-0.7793 7.65625,-0.7793 3.5257,0 6.38289,0.59579 8.44336,1.68555 1.98721,1.13555 3.47109,2.55414 4.35938,4.19336 0.42126,0.77839 0.71467,1.72299 0.88867,2.75781 0.20147,1.10807 0.27344,2.40673 0.27344,3.82617 v 2.41016 c -1.64838,-0.55861 -3.4979,-0.97989 -5.4668,-1.38282 -2.48172,-0.45789 -5.23692,-0.72374 -8.1582,-0.76953 -3.14108,0 -6.01763,0.43949 -8.63672,1.26367 -2.58246,0.86998 -4.88037,2.0959 -6.78516,3.7168 -1.89564,1.59344 -3.38046,3.60976 -4.43359,5.88086 -1.02566,2.29858 -1.5918,4.94333 -1.5918,7.85547 0,2.91213 0.49366,5.5691 1.42774,7.9043 0.93409,2.33519 2.30779,4.35957 4.00195,5.98046 1.72164,1.62091 3.79102,2.90164 6.14453,3.74415 2.32605,0.81502 4.92688,1.25585 7.73828,1.25585 2.033,0 4.00185,-0.23877 5.82422,-0.6875 1.81322,-0.4304 3.50778,-1.04473 5.03711,-1.77734 1.53849,-0.75093 2.9118,-1.62903 4.08398,-2.57226 0.59524,-0.4762 1.11682,-0.95214 1.61133,-1.4375 l 0.80664,5.02734 h 9.59766 v -37.99609 c 0,-1.82237 -0.14715,-3.52598 -0.42188,-5.06446 -0.2381,-1.57512 -0.72298,-3.07644 -1.3457,-4.40429 -1.61175,-3.45244 -4.35951,-6.23689 -8.06836,-8.33399 -3.69969,-2.02385 -8.4703,-3.08594 -14.23047,-3.08594 z m 344.125,0.006 c -3.25096,0.0458 -6.54162,0.68095 -9.5957,1.93555 -3.13192,1.28207 -5.93517,3.24237 -8.37109,5.83398 -2.40847,2.5733 -4.35839,5.83434 -5.81446,9.63477 -1.38281,3.81874 -2.125,8.37818 -2.125,13.51563 0,5.21986 0.76916,9.78064 2.23438,13.50781 1.47439,3.77295 3.60027,6.85021 6.22851,9.21289 2.66488,2.3352 5.85148,4.06539 9.46875,5.10937 3.53485,1.04397 7.38066,1.57617 11.4375,1.57617 3.3517,0 6.58445,-0.28438 9.53321,-0.86132 3.00371,-0.57693 5.54878,-1.40099 7.63672,-2.38086 l 0.56835,-0.32032 v -9.67968 L 995.25586,262.5 c -2.39931,0.86998 -4.81Line truncated
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) by Marsupilami -->
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.0"
|
||||
|
||||
|
||||
viewBox="-46.62 -46.62 1680 1647.24"
|
||||
id="svg9522">
|
||||
<defs
|
||||
id="defs9524" />
|
||||
<path
|
||||
d="M 1014.2288,261 C 1014.2288,261 986.22877,230 949.22877,230 C 949.22877,230 868.22877,230 813.22877,405 C 813.22877,405 766.22877,558 766.22877,785 C 766.22877,785 766.22877,999 810.22877,1150 C 810.22877,1150 854.22877,1303 918.22877,1303 C 918.22877,1303 963.22877,1303 1001.2288,1219 C 1001.2288,1219 1039.2288,1135 1055.2288,998 L 1175.2288,968 C 1175.2288,968 1144.2288,1223 1048.2288,1388 C 1048.2288,1388 949.22877,1554 824.22877,1554 C 824.22877,1554 674.22877,1554 568.22877,1329 C 568.22877,1329 462.22877,1104 462.22877,785 C 462.22877,785 462.22877,466 568.22877,239 C 568.22877,239 674.22877,13 824.22877,13 C 824.22877,13 974.22877,13 1081.2288,243 L 1014.2288,261"
|
||||
id="path8564"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 1436.2288,568 C 1436.2288,568 1229.2288,739 899.22877,822 C 899.22877,822 568.22877,904 305.22877,850 C 305.22877,850 42.228768,794 3.2287685,633 C 3.2287685,633 -29.771232,499 115.22877,351 C 115.22877,351 258.22877,204 515.22877,106 L 532.22877,172 C 532.22877,172 382.22877,236 299.22877,317 C 299.22877,317 214.22877,402 233.22877,475 C 233.22877,475 258.22877,568 434.22877,596 C 434.22877,596 609.22877,623 836.22877,567 C 836.22877,567 1061.2288,511 1205.2288,404 C 1205.2288,404 1347.2288,296 1323.2288,202 C 1323.2288,202 1303.2288,121 1159.2288,86 L 1137.2288,-8.5265128e-14 C 1137.2288,-8.5265128e-14 1324.2288,7 1448.2288,69 C 1448.2288,69 1575.2288,131 1601.2288,236 C 1601.2288,236 1642.2288,396 1436.2288,568"
|
||||
id="path8566"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2620.2288,207 L 2620.2288,209 L 2628.2288,212 L 2638.2288,209 L 2643.2288,206 L 2653.2288,207 C 2653.2288,207 2665.2288,210 2675.2288,207 L 2678.2288,207 L 2628.2288,201 L 2620.2288,207"
|
||||
id="path8568"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2307.2288,35 C 2304.2288,38 2300.2288,39 2300.2288,39 L 2313.2288,69 L 2304.2288,69 L 2313.2288,98 C 2319.2288,131 2319.2288,196 2319.2288,196 L 2315.2288,213 L 2310.2288,231 L 2313.2288,264 C 2312.2288,277 2297.2288,292 2297.2288,292 L 2297.2288,308 C 2239.2288,329 2231.2288,340 2231.2288,340 L 2200.2288,349 C 2186.2288,358 2157.2288,369 2157.2288,369 C 2138.2288,388 2118.2288,391 2118.2288,391 L 2115.2288,393 L 2112.2288,393 L 2112.2288,394 L 2104.2288,394 C 2115.2288,372 2104.2288,349 2104.2288,349 L 2097.2288,337 L 2089.2288,307 C 2082.2288,283 2077.2288,281 2077.2288,281 L 2068.2288,295 L 2061.2288,313 L 2055.2288,313 L 2042.2288,346 L 2030.2288,376 L 2014.2288,420 C 2015.2288,425 1996.2288,464 1996.2288,464 L 1988.2288,472 C 1988.2288,500 1976.2288,508 1976.2288,508 L 1968.2288,528 C 1962.2288,543 1961.2288,541 1961.2288,541 C 1962.2288,549 1956.2288,591 1956.2288,591 C 1953.2288,633 1967.2288,641 1967.2288,641 C 1973.2288,650 1970.2288,653 1970.2288,653 L 1985.2288,683 L 1990.2288,682 L 1996.2288,692 C 2006.2288,715 2027.2288,709 2027.2288,709 L 2065.2288,698 L 2073.2288,732 C 2077.2288,754 2085.2288,760 2085.2288,760 L 2114.2288,766 L 2144.2288,769 L 2173.2288,756 L 2238.2288,741 C 2280.2288,732 2301.2288,721 2301.2288,721 C 2318.2288,753 2315.2288,779 2315.2288,779 L 2312.2288,831 L 2318.2288,853 L 2322.2288,874 L 2324.2288,971 L 2334.2288,1001 L 2330.2288,1013 L 2330.2288,1049 L 2340.2288,1079 L 2343.2288,1113 L 2349.2288,1113 L 2346.2288,1082 L 2357.2288,1084 L 2359.2288,1099 L 2362.2288,1098 L 2360.2288,1079 C 2349.2288,1075 2349.2288,1055 2349.2288,1055 C 2349.2288,1037 2357.2288,1025 2357.2288,1025 C 2363.2288,1027 2372.2288,1060 2372.2288,1060 C 2380.2288,1085 2381.2288,1101 2381.2288,1101 L 2402.2288,1101 L 2402.2288,1110 L 2396.2288,1105 C 2386.2288,1111 2384.2288,1143 2384.2288,1143 C 2383.2288,1175 2389.2288,1176 2389.2288,1176 C 2392.2288,1176 2408.2288,1164 2408.2288,1164 L 2404.2288,1170 L 2404.2288,1173 L 2410.2288,1173 L 2413.2288,1190 C 2420.2288,1190 2420.2288,1149 2420.2288,1149 L 2424.2288,1149 L 2431.2288,1155 L 2434.2288,1152 L 2434.2288,1113 L 2442.2288,1120 L 2452.2288,1111 L 2458.2288,1098 C 2460.2288,1089 2455.2288,1063 2455.2288,1063 C 2463.2288,1023 2464.2288,1002 2464.2288,1002 L 2464.2288,980 L 2493.2288,915 C 2501.2288,890 2502.2288,850 2502.2288,850 L 2505.2288,783 L 2517.2288,766 C 2528.2288,756 2529.2288,750 2529.2288,750 L 2552.2288,685 C 2570.2288,638 2582.2288,633 2582.2288,633 L 2640.2288,620 C 2678.2288,608 2675.2288,588 2675.2288,588 C 2694.2288,588 2702.2288,505 2702.2288,505 L 2718.2288,423 L 2732.2288,396 L 2738.2288,333 L 2749.2288,271 C 2752.2288,242 2735.2288,228 2735.2288,228 C 2724.2288,218 2693.2288,210 2693.2288,210 L 2690.2288,212 C 2697.2288,216 2712.2288,221 2712.2288,221 C 2726.2288,228 2729.2288,239 2729.2288,239 C 2733.2288,255 2723.2288,281 2723.2288,281 C 2714.2288,304 2699.2288,317 2699.2288,317 L 2700.2288,328 C 2696.2288,342 2673.2288,369 2673.2288,369 L 2641.2288,413 L 2613.2288,431 C 2575.2288,432 2563.2288,444 2563.2288,444 L 2558.2288,440 C 2552.2288,431 2552.2288,385 2552.2288,385 L 2548.2288,349 C 2554.2288,340 2554.2288,334 2554.2288,334 L 2554.2288,314 C 2564.2288,302 2623.2288,280 2623.2288,280 C 2676.2288,258 2682.2288,237 2682.2288,237 L 2685.2288,240 C 2684.2288,249 2693.2288,251 2693.2288,251 C 2693.2288,236 2703.2288,236 2703.2288,236 L 2724.2288,240 L 2724.2288,237 L 2690.2288,222 C 2690.2288,230 2684.2288,230 2684.2288,230 L 2671.2288,228 L 2670.2288,234 L 2675.2288,237 L 2668.2288,248 L 2665.2288,248 C 2665.2288,243 2646.2288,243 2646.2288,243 L 2652.2288,221 C 2635.2288,213 2634.2288,228 2634.2288,228 L 2632.2288,228 L 2629.2288,222 L 2626.2288,222 L 2625.2288,227 L 2613.2288,227 L 2605.2288,236 L 2596.2288,233 L 2602.2288,227 L 2596.2288,215 C 2608.2288,215 2614.2288,201 2614.2288,201 L 2540.2288,206 C 2540.2288,216 2561.2288,213 2561.2288,213 C 2584.2288,210 2587.2288,218 2587.2288,218 L 2564.2288,216 L 2543.2288,219 L 2540.2288,228 L 2534.2288,224 L 2528.2288,219 L 2513.2288,213 C 2504.2288,219 2499.2288,218 2499.2288,218 L 2489.2288,212 L 2479.2288,202 C 2467.2288,193 2414.2288,103 2414.2288,103 C 2375.2288,38 2307.2288,35 2307.2288,35 z M 2297.2288,402 L 2309.2288,450 L 2316.2288,482 C 2322.2288,502 2319.2288,515 2319.2288,515 L 2300.2288,543 L 2277.2288,573 C 2250.2288,590 2230.2288,596 2230.2288,596 L 2179.2288,603 C 2154.2288,606 2126.2288,632 2126.2288,632 C 2098.2288,656 2088.2288,655 2088.2288,655 C 2083.2288,658 2077.2288,659 2077.2288,659 C 2071.2288,644 2083.2288,635 2083.2288,635 C 2079.2288,629 2086.2288,612 2086.2288,612 L 2101.2288,594 L 2112.2288,573 L 2118.2288,547 C 2129.2288,540 2127.2288,532 2127.2288,532 L 2179.2288,472 L 2242.2288,444 C 2287.2288,425 2297.2288,402 2297.2288,402 z"
|
||||
id="path8570"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3320.2288,201 L 3359.2288,186 L 3399.2288,175 L 3435.2288,171 L 3450.2288,165 L 3491.2288,163 L 3532.2288,165 L 3548.2288,166 L 3565.2288,172 L 3577.2288,190 L 3591.2288,216 C 3591.2288,216 3583.2288,255 3591.2288,263 C 3591.2288,263 3585.2288,275 3582.2288,352 L 3574.2288,435 L 3568.2288,494 L 3573.2288,577 C 3573.2288,577 3573.2288,593 3565.2288,620 C 3565.2288,620 3574.2288,662 3565.2288,671 C 3565.2288,671 3585.2288,797 3582.2288,804 L 3571.2288,831 L 3562.2288,857 L 3561.2288,872 L 3565.2288,877 L 3562.2288,895 L 3564.2288,912 C 3564.2288,912 3562.2288,925 3556.2288,925 L 3541.2288,915 L 3529.2288,901 L 3529.2288,900 C 3529.2288,900 3503.2288,878 3491.2288,884 C 3491.2288,884 3491.2288,921 3515.2288,958 L 3520.2288,961 C 3520.2288,961 3523.2288,978 3551.2288,1022 L 3554.2288,1028 L 3577.2288,1096 C 3577.2288,1096 3594.2288,1120 3660.2288,1150 L 3666.2288,1149 L 3666.2288,1144 C 3666.2288,1144 3672.2288,1144 3686.2288,1128 C 3686.2288,1128 3704.2288,1105 3706.2288,1079 L 3706.2288,1028 L 3712.2288,992 L 3712.2288,955 L 3703.2288,922 C 3703.2288,922 3698.2288,896 3709.2288,892 C 3709.2288,892 3709.2288,839 3704.2288,836 C 3704.2288,836 3713.2288,795 3707.2288,748 L 3709.2288,711 L 3709.2288,673 L 3703.2288,590 L 3701.2288,505 L 3698.2288,429 L 3689.2288,357 C 3689.2288,357 3692.2288,336 3697.2288,328 C 3697.2288,328 3694.2288,316 3698.2288,278 C 3698.2288,278 3685.2288,277 3692.2288,230 L 3701.2288,172 C 3701.2288,172 3698.2288,157 3672.2288,124 L 3635.2288,83 C 3635.2288,83 3612.2288,71 3600.2288,74 L 3520.2288,86 C 3520.2288,86 3464.2288,92 3449.2288,109 L 3429.2288,107 L 3408.2288,112 L 3405.2288,115 L 3367.2288,119 L 3337.2288,125 L 3326.2288,125 L 3287.2288,139 L 3252.2288,156 L 3217.2288,169 C 3217.2288,169 3186.2288,195 3170.2288,192 C 3170.2288,192 3102.2288,209 3075.2288,225 C 3075.2288,225 3075.2288,227 3093.2288,231 C 3093.2288,231 3111.2288,236 3113.2288,246 L 3133.2288,252 C 3133.2288,252 3181.2288,260 3208.2288,242 C 3208.2288,242 3214.2288,245 3248.2288,230 C 3248.2288,230 3255.2288,221 3272.2288,221 L 3293.2288,209 L 3320.2288,201"
|
||||
id="path8572"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2443.2288,1125 L 2442.2288,1166 C 2442.2288,1166 2449.2288,1166 2449.2288,1147 C 2449.2288,1147 2451.2288,1129 2443.2288,1125"
|
||||
id="path8576"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2355.2288,1147 L 2360.2288,1164 L 2362.2288,1164 L 2366.2288,1155 L 2366.2288,1137 L 2363.2288,1137 L 2362.2288,1138 L 2365.2288,1128 L 2351.2288,1120 C 2351.2288,1120 2348.2288,1129 2355.2288,1147"
|
||||
id="path8578"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2375.2288,1176 L 2377.2288,1173 C 2377.2288,1173 2380.2288,1143 2374.2288,1137 C 2374.2288,1137 2366.2288,1144 2372.2288,1176 L 2375.2288,1176"
|
||||
id="path8580"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2354.2288,1246 L 2354.2288,1358 L 2448.2288,1358 L 2448.2288,1246 L 2529.2288,1246 L 2529.2288,1515 L 2448.2288,1515 L 2448.2288,1400 L 2354.2288,1400 L 2354.2288,1515 L 2272.2288,1515 L 2272.2288,1246 L 2354.2288,1246"
|
||||
id="path8582"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2079.2288,1309 C 2079.2288,1309 2055.2288,1333 2055.2288,1380 L 2055.2288,1383 C 2055.2288,1383 2055.2288,1432 2077.2288,1456 C 2077.2288,1456 2103.2288,1479 2141.2288,1479 L 2188.2288,1474 L 2218.2288,1467 L 2218.2288,1507 L 2188.2288,1516 L 2136.2288,1519 C 2136.2288,1519 2062.2288,1519 2015.2288,1486 C 2015.2288,1486 1970.2288,1453 1970.2288,1388 L 1970.2288,1376 C 1970.2288,1376 1970.2288,1315 2017.2288,1279 C 2017.2288,1279 2064.2288,1241 2135.2288,1241 L 2186.2288,1244 L 2218.2288,1252 L 2218.2288,1294 L 2189.2288,1287 L 2148.2288,1284 C 2148.2288,1284 2104.2288,1284 2079.2288,1309"
|
||||
id="path8584"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3489.2288,541 C 3489.2288,541 3491.2288,558 3471.2288,580 L 3465.2288,588 L 3464.2288,591 L 3458.2288,603 L 3455.2288,617 L 3446.2288,623 L 3443.2288,629 L 3446.2288,630 C 3446.2288,630 3456.2288,633 3473.2288,624 L 3489.2288,620 L 3508.2288,618 L 3521.2288,617 L 3529.2288,615 C 3529.2288,615 3547.2288,621 3561.2288,600 L 3564.2288,573 L 3561.2288,558 L 3556.2288,547 L 3550.2288,544 L 3541.2288,540 L 3520.2288,538 L 3517.2288,535 L 3497.2288,526 L 3482.2288,520 L 3489.2288,541"
|
||||
id="path8586"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3420.2288,242 L 3394.2288,249 L 3365.2288,255 L 3310.2288,267 L 3282.2288,283 L 3258.2288,289 C 3258.2288,289 3254.2288,292 3248.2288,293 L 3237.2288,295 L 3226.2288,298 L 3186.2288,316 L 3166.2288,328 L 3152.2288,331 L 3148.2288,334 L 3146.2288,336 L 3139.2288,345 L 3125.2288,352 L 3108.2288,364 C 3108.2288,364 3077.2288,364 3083.2288,387 L 3089.2288,387 C 3089.2288,387 3089.2288,391 3127.2288,407 L 3128.2288,425 C 3128.2288,425 3131.2288,438 3140.2288,444 L 3180.2288,444 L 3193.2288,446 L 3214.2288,446 C 3214.2288,446 3220.2288,446 3220.2288,450 L 3223.2288,470 L 3225.2288,485 L 3223.2288,508 L 3216.2288,523 L 3199.2288,526 L 3186.2288,534 L 3148.2288,549 L 3133.2288,561 L 3110.2288,576 L 3098.2288,588 L 3099.2288,593 L 3099.2288,597 L 3087.2288,608 L 3089.2288,620 L 3093.2288,623 L 3107.2288,636 L 3127.2288,639 L 3136.2288,655 L 3148.2288,667 L 3163.2288,670 L 3175.2288,668 L 3173.2288,677 C 3173.2288,677 3169.2288,686 3173.2288,686 L 3167.2288,706 L 3160.2288,720 L 3157.2288,727 L 3139.2288,741 L 3130.2288,753 L 3121.2288,769 L 3121.2288,776 L 3114.2288,791 L 3108.2288,800 L 3108.2288,812 L 3116.2288,834 L 3125.2288,848 C 3125.2288,848 3131.2288,856 3133.2288,854 C 3133.2288,854 3133.2288,856 3143.2288,860 C 3143.2288,860 3164.2288,863 3176.2288,860 L 3217.2288,851 L 3240.2288,839 L 3281.2288,825 L 3290.2288,818 C 3290.2288,818 3296.2288,818 3320.2288,800 C 3320.2288,800 3325.2288,803 3337.2288,791 C 3337.2288,791 3337.2288,792 3353.2288,783 L 3367.2288,779 C 3367.2288,779 3375.2288,776 3382.2288,771 L 3402.2288,765 L 3429.2288,747 L 3449.2288,730 C 3449.2288,730 3459.2288,724 3462.2288,712 L 3467.2288,703 L 3470.2288,698 L 3458.2288,698 C 3458.2288,698 3450.2288,698 3455.2288,686 L 3462.2288,673 L 3438.2288,671 C 3438.2288,671 3423.2288,671 3423.2288,665 L 3381.2288,668 L 3385.2288,652 L 3387.2288,639 L 3393.2288,623 C 3393.2288,623 3388.2288,623 3388.2288,614 L 3394.2288,609 L 3405.2288,608 L 3429.2288,574 L 3455.2288,547 L 3456.2288,540 L 3462.2288,528 L 3462.2288,515 L 3459.2288,500 L 3443.2288,484 L 3414.2288,464 L 3402.2288,458 L 3397.2288,453 L 3387.2288,446 L 3390.2288,441 C 3390.2288,441 3390.2288,428 3397.2288,413 L 3427.2288,381 C 3427.2288,381 3435.2288,381 3449.2288,352 L 3459.2288,339 L 3462.2288,326 L 3461.2288,313 L 3464.2288,304 L 3462.2288,290 L 3447.2288,264 L 3440.2288,254 C 3440.2288,254 3440.2288,242 3420.2288,242"
|
||||
id="path8588"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3172.2288,925 L 3149.2288,931 L 3114.2288,931 L 3098.2288,934 L 3080.2288,933 L 3099.2288,975 C 3099.2288,975 3118.2288,1010 3124.2288,1011 L 3142.2288,1016 L 3170.2288,1008 C 3170.2288,1008 3190.2288,1004 3193.2288,998 L 3199.2288,998 C 3199.2288,998 3225.2288,992 3231.2288,983 L 3263.2288,975 L 3293.2288,961 L 3307.2288,957 L 3329.2288,946 L 3353.2288,931 L 3415.2288,887 C 3415.2288,887 3440.2288,871 3497.2288,844 L 3511.2288,831 L 3511.2288,828 L 3502.2288,828 C 3502.2288,828 3494.2288,834 3462.2288,842 L 3432.2288,854 L 3423.2288,856 L 3396.2288,866 L 3365.2288,871 L 3323.2288,889 L 3302.2288,893 L 3281.2288,903 L 3269.2288,904 L 3240.2288,910 C 3240.2288,910 3216.2288,922 3210.2288,924 L 3196.2288,921 L 3172.2288,925"
|
||||
id="path8590"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3024.2288,283 L 3015.2288,310 C 3015.2288,310 3010.2288,325 3012.2288,342 L 3015.2288,354 L 3015.2288,396 L 3018.2288,422 L 3013.2288,441 L 3012.2288,473 L 3006.2288,487 L 2997.2288,525 L 3000.2288,532 L 3000.2288,564 L 2995.2288,582 L 2995.2288,596 L 2992.2288,624 L 2987.2288,644 L 2983.2288,707 L 2981.2288,707 L 2978.2288,726 L 2971.2288,742 C 2971.2288,742 2968.2288,765 2974.2288,779 L 2975.2288,798 L 2977.2288,824 C 2977.2288,824 2978.2288,838 2972.2288,845 L 2969.2288,869 C 2969.2288,869 2966.2288,924 2998.2288,951 C 2998.2288,951 3009.2288,961 3018.2288,958 L 3025.2288,949 C 3025.2288,949 3037.2288,933 3037.2288,904 L 3033.2288,856 L 3030.2288,842 L 3036.2288,797 C 3036.2288,797 3036.2288,777 3039.2288,769 C 3039.2288,769 3024.2288,759 3034.2288,711 L 3036.2288,683 L 3037.2288,665 C 3037.2288,665 3040.2288,650 3042.2288,650 L 3045.2288,635 L 3048.2288,624 L 3063.2288,553 L 3077.2288,512 L 3077.2288,505 L 3071.2288,494 L 3071.2288,478 L 3075.2288,467 L 3080.2288,443 L 3078.2288,443 L 3080.2288,425 L 3078.2288,407 C 3078.2288,407 3071.2288,407 3071.2288,358 L 3062.2288,329 L 3054.2288,320 L 3045.2288,302 L 3036.2288,274 L 3028.2288,267 L 3024.2288,283"
|
||||
id="path8592"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2661.2288,233 L 2662.2288,227 L 2658.2288,219 L 2658.2288,231 L 2661.2288,233"
|
||||
id="path8594"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2678.2288,224 L 2664.2288,219 L 2664.2288,224 L 2678.2288,224"
|
||||
id="path8596"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3051.2288,1246 L 3051.2288,1515 L 3016.2288,1515 L 2835.2288,1346 L 2835.2288,1515 L 2776.2288,1515 L 2776.2288,1246 L 2820.2288,1246 L 2994.2288,1406 L 2994.2288,1246 L 3051.2288,1246"
|
||||
id="path8598"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 2696.2288,1246 L 2696.2288,1515 L 2614.2288,1515 L 2614.2288,1246 L 2696.2288,1246"
|
||||
id="path8600"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3205.2288,1246 L 3083.2288,1515 L 3149.2288,1515 L 3176.2288,1451 L 3297.2288,1451 L 3323.2288,1515 L 3408.2288,1515 L 3281.2288,1246 L 3205.2288,1246 z M 3235.2288,1312 L 3281.2288,1412 L 3193.2288,1412 L 3235.2288,1312 z"
|
||||
id="path8602"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3535.2288,1246 L 3777.2288,1246 L 3777.2288,1285 L 3698.2288,1285 L 3698.2288,1515 L 3616.2288,1515 L 3616.2288,1285 L 3535.2288,1285 L 3535.2288,1246"
|
||||
id="path8604"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 5302.2288,183 L 5313.2288,169 L 5324.2288,171 L 5331.2288,169 L 5364.2288,166 C 5364.2288,166 5366.2288,172 5334.2288,172 L 5316.2288,180 L 5327.2288,186 L 5361.2288,190 C 5361.2288,190 5372.2288,192 5387.2288,204 L 5407.2288,225 L 5392.2288,242 L 5379.2288,248 L 5366.2288,249 L 5314.2288,260 L 5274.2288,263 L 5242.2288,272 L 5175.2288,280 L 5147.2288,289 L 5130.2288,299 L 5127.2288,299 L 5124.2288,293 L 5116.2288,301 C 5116.2288,301 5113.2288,307 5113.2288,316 C 5113.2288,316 5113.2288,329 5121.2288,334 L 5139.2288,339 C 5139.2288,339 5151.2288,345 5157.2288,345 L 5195.2288,334 L 5236.2288,326 L 5274.2288,316 L 5336.2288,307 L 5532.2288,302 L 5582.2288,295 L 5597.2288,298 L 5611.2288,299 C 5611.2288,299 5629.2288,290 5635.2288,293 L 5637.2288,283 L 5637.2288,263 L 5649.2288,255 C 5649.2288,255 5673.2288,239 5664.2288,227 L 5632.2288,218 L 5596.2288,219 L 5544.2288,219 C 5544.2288,219 5525.2288,219 5526.2288,209 L 5528.2288,195 C 5528.2288,195 5522.2288,177 5525.2288,171 L 5523.2288,140 L 5503.2288,125 L 5491.2288,124 L 5478.2288,113 L 5422.2288,89 L 5401.2288,83 L 5389.2288,86 L 5370.2288,118 L 5369.2288,121 L 5358.2288,118 L 5345.2288,127 L 5346.2288,128 L 5339.2288,133 L 5316.2288,150 C 5316.2288,150 5302.2288,163 5313.2288,160 L 5316.2288,160 L 5310.2288,165 L 5281.2288,175 L 5278.2288,184 L 5286.2288,187 L 5302.2288,183"
|
||||
id="path8608"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 4244.2288,171 L 4241.2288,204 C 4241.2288,204 4224.2288,231 4220.2288,245 C 4220.2288,245 4206.2288,352 4197.2288,363 L 4180.2288,378 L 4161.2288,387 L 4140.2288,401 L 4088.2288,438 L 4087.2288,440 L 4108.2288,443 L 4120.2288,456 L 4144.2288,449 L 4170.2288,446 L 4171.2288,466 L 4176.2288,473 L 4159.2288,500 C 4159.2288,500 4159.2288,523 4078.2288,577 C 4078.2288,577 4075.2288,568 4078.2288,537 L 4084.2288,496 L 4082.2288,479 L 4070.2288,458 C 4070.2288,458 4056.2288,440 4055.2288,432 C 4055.2288,432 4038.2288,429 4037.2288,414 L 4028.2288,396 C 4028.2288,396 4025.2288,396 4020.2288,414 L 4011.2288,435 L 4008.2288,446 L 4005.2288,447 C 4005.2288,447 4005.2288,456 3993.2288,472 L 3981.2288,493 C 3981.2288,493 3978.2288,506 3981.2288,565 L 3976.2288,624 L 3967.2288,682 L 3955.2288,721 C 3955.2288,721 3946.2288,741 3955.2288,763 C 3955.2288,763 3960.2288,772 3979.2288,783 C 3979.2288,783 3998.2288,792 4008.2288,794 L 4020.2288,807 C 4020.2288,807 4026.2288,822 4031.2288,824 L 4061.2288,845 L 4090.2288,850 L 4114.2288,859 L 4131.2288,928 C 4131.2288,928 4146.2288,987 4165.2288,993 L 4167.2288,993 C 4167.2288,993 4168.2288,999 4179.2288,1007 L 4194.2288,1016 C 4194.2288,1016 4221.2288,1034 4242.2288,1040 L 4295.2288,1048 L 4341.2288,1049 L 4394.2288,1054 C 4394.2288,1054 4439.2288,1049 4444.2288,1040 L 4490.2288,1034 C 4490.2288,1034 4540.2288,1022 4537.2288,1017 L 4543.2288,1016 L 4633.2288,993 L 4655.2288,984 L 4651.2288,984 L 4655.2288,975 L 4658.2288,975 L 4657.2288,969 L 4660.2288,969 L 4663.2288,972 L 4664.2288,969 L 4663.2288,966 L 4667.2288,960 L 4663.2288,961 L 4670.2288,948 L 4681.2288,937 L 4682.2288,937 L 4682.2288,943 L 4690.2288,933 L 4692.2288,933 L 4692.2288,934 L 4688.2288,937 L 4690.2288,940 C 4690.2288,940 4688.2288,943 4692.2288,942 L 4693.2288,936 L 4698.2288,933 L 4699.2288,934 L 4704.2288,928 L 4707.2288,927 L 4707.2288,918 L 4701.2288,915 L 4698.2288,919 L 4698.2288,912 L 4695.2288,910 L 4695.2288,913 L 4692.2288,912 L 4692.2288,907 L 4688.2288,903 L 4695.2288,898 L 4693.2288,895 L 4685.2288,903 L 4688.2288,893 L 4693.2288,893 L 4695.2288,890 L 4682.2288,889 L 4678.2288,892 L 4676.2288,890 L 4678.2288,883 L 4669.2288,881 L 4675.2288,878 L 4672.2288,875 L 4672.2288,872 L 4676.2288,874 L 4676.2288,872 L 4670.2288,866 L 4669.2288,868 L 4664.2288,862 L 4663.2288,860 L 4657.2288,862 L 4655.2288,859 L 4655.2288,863 L 4660.2288,863 L 4660.2288,872 L 4658.2288,872 L 4652.2288,860 L 4649.2288,859 L 4646.2288,860 L 4652.2288,856 L 4654.2288,856 L 4643.2288,854 C 4643.2288,854 4637.2288,854 4636.2288,859 L 4625.2288,865 L 4608.2288,877 L 4590.2288,881 L 4572.2288,889 L 4513.2288,900 L 4463.2288,915 L 4445.2288,918 L 4348.2288,928 L 4323.2288,919 C 4323.2288,919 4318.2288,919 4315.2288,915 L 4312.2288,904 C 4312.2288,904 4292.2288,863 4295.2288,853 L 4323.2288,816 L 4338.2288,803 C 4338.2288,803 4348.2288,795 4365.2288,797 L 4382.2288,794 L 4403.2288,791 L 4416.2288,783 L 4433.2288,780 L 4450.2288,776 L 4466.2288,772 L 4534.2288,763 L 4543.2288,756 L 4551.2288,744 L 4565.2288,724 C 4565.2288,724 4580.2288,712 4581.2288,700 L 4581.2288,685 L 4586.2288,667 L 4571.2288,632 L 4555.2288,599 L 4584.2288,525 L 4589.2288,506 C 4589.2288,506 4593.2288,491 4598.2288,491 L 4602.2288,470 L 4607.2288,447 C 4607.2288,447 4613.2288,434 4611.2288,426 L 4608.2288,407 L 4611.2288,394 L 4607.2288,379 C 4607.2288,379 4601.2288,370 4601.2288,364 L 4586.2288,342 L 4574.2288,308 C 4574.2288,308 4568.2288,293 4543.2288,283 L 4539.2288,286 L 4516.2288,277 C 4516.2288,277 4501.2288,267 4487.2288,271 L 4456.2288,266 L 4401.2288,292 L 4341.2288,325 L 4323.2288,325 L 4335.2288,248 C 4335.2288,248 4338.2288,207 4295.2288,187 L 4271.2288,175 L 4244.2288,171 z M 4430.2288,354 L 4475.2288,364 L 4474.2288,370 L 4483.2288,382 L 4483.2288,391 C 4483.2288,391 4483.2288,399 4471.2288,411 L 4457.2288,411 L 4454.2288,411 C 4454.2288,411 4407.2288,444 4379.2288,450 L 4356.2288,453 L 4345.2288,453 C 4345.2288,453 4333.2288,456 4320.2288,453 C 4320.2288,453 4301.2288,450 4298.2288,440 L 4301.2288,419 C 4301.2288,419 4306.2288,404 4310.2288,402 C 4310.2288,402 4310.2288,394 4324.2288,388 L 4338.2288,382 C 4338.2288,382 4350.2288,376 4372.2288,370 L 4389.2288,361 L 4412.2288,355 L 4430.2288,354 z M 4439.2288,526 L 4442.2288,529 C 4442.2288,529 4447.2288,534 4444.2288,540 L 4434.2288,588 L 4427.2288,600 C 4427.2288,600 4419.2288,611 4379.2288,629 C 4379.2288,629 4338.2288,656 4315.2288,639 L 4307.2288,618 L 4312.2288,602 C 4312.2288,602 4310.2288,588 4314.2288,580 L 4327.2288,565 L 4360.2288,547 L 4403.2288,532 L 4439.2288,526 z M 4114.2288,630 L 4134.2288,638 C 4134.2288,638 4143.2288,647 4137.2288,674 L 4125.2288,711 L 4123.2288,718 L 4114.2288,732 L 4093.2288,736 C 4093.2288,736 4087.2288,736 4067.2288,747 C 4067.2288,747 4058.2288,753 4047.2288,757 L 4040.2288,765 L 4038.2288,763 L 4046.2288,738 L 4043.2288,723 L 4047.2288,709 L 4049.2288,695 L 4061.2288,662 L 4066.2288,647 L 4067.2288,647 C 4067.2288,647 4078.2288,647 4093.2288,6Line truncated
|
||||
id="path8610"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 4982.2288,283 L 4982.2288,292 C 4982.2288,292 4970.2288,328 4970.2288,345 L 4950.2288,376 L 4947.2288,387 L 4941.2288,399 L 4924.2288,435 L 4897.2288,494 L 4882.2288,512 L 4859.2288,547 L 4852.2288,570 L 4844.2288,594 L 4846.2288,617 L 4847.2288,652 C 4847.2288,652 4852.2288,655 4855.2288,656 L 4865.2288,671 L 4867.2288,703 L 4865.2288,736 L 4870.2288,729 L 4874.2288,717 L 4874.2288,715 L 4876.2288,715 L 4877.2288,717 L 4874.2288,723 L 4876.2288,726 L 4879.2288,726 L 4884.2288,717 L 4893.2288,689 L 4909.2288,671 L 4918.2288,665 L 4980.2288,605 C 4980.2288,605 4998.2288,599 5009.2288,624 L 5012.2288,647 L 5018.2288,692 L 5018.2288,750 L 5011.2288,783 L 5004.2288,842 L 4995.2288,869 L 4983.2288,939 L 4982.2288,1014 C 4982.2288,1014 4982.2288,1046 5012.2288,1049 L 5026.2288,1033 C 5026.2288,1033 5048.2288,1034 5039.2288,1022 L 5039.2288,1010 L 5051.2288,1001 L 5062.2288,1001 L 5077.2288,977 L 5083.2288,931 L 5092.2288,831 L 5095.2288,797 L 5101.2288,763 L 5112.2288,630 C 5112.2288,630 5107.2288,600 5109.2288,588 L 5118.2288,567 C 5118.2288,567 5122.2288,558 5122.2288,534 L 5119.2288,523 L 5110.2288,515 L 5101.2288,505 C 5101.2288,505 5094.2288,502 5085.2288,497 L 5056.2288,484 L 5038.2288,488 L 5042.2288,470 L 5048.2288,455 L 5050.2288,425 L 5054.2288,410 C 5054.2288,410 5056.2288,390 5044.2288,364 L 5023.2288,322 L 5021.2288,310 L 5011.2288,284 L 4988.2288,264 L 4982.2288,283"
|
||||
id="path8612"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 4177.2288,1246 L 4177.2288,1476 L 4312.2288,1476 L 4312.2288,1515 L 4096.2288,1515 L 4096.2288,1246 L 4177.2288,1246"
|
||||
id="path8620"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 3896.2288,1285 L 3896.2288,1359 L 4004.2288,1359 L 4004.2288,1400 L 3896.2288,1400 L 3896.2288,1476 L 4044.2288,1476 L 4044.2288,1515 L 3815.2288,1515 L 3815.2288,1246 L 4035.2288,1246 L 4035.2288,1285 L 3896.2288,1285"
|
||||
id="path8622"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 4366.2288,1246 L 4587.2288,1246 L 4587.2288,1285 L 4448.2288,1285 L 4448.2288,1359 L 4555.2288,1359 L 4555.2288,1400 L 4448.2288,1400 L 4448.2288,1476 L 4598.2288,1476 L 4598.2288,1515 L 4366.2288,1515 L 4366.2288,1246"
|
||||
id="path8624"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 4741.2288,1309 C 4741.2288,1309 4716.2288,1335 4716.2288,1380 L 4716.2288,1383 C 4716.2288,1383 4716.2288,1432 4738.2288,1456 C 4738.2288,1456 4766.2288,1479 4802.2288,1479 L 4849.2288,1474 L 4879.2288,1467 L 4879.2288,1507 L 4849.2288,1516 L 4797.2288,1519 C 4797.2288,1519 4722.2288,1519 4678.2288,1486 C 4678.2288,1486 4631.2288,1453 4631.2288,1388 L 4631.2288,1376 C 4631.2288,1376 4631.2288,1315 4678.2288,1279 C 4678.2288,1279 4725.2288,1241 4797.2288,1241 L 4847.2288,1244 L 4879.2288,1252 L 4879.2288,1294 L 4852.2288,1287 L 4811.2288,1284 C 4811.2288,1284 4767.2288,1284 4741.2288,1309"
|
||||
id="path8626"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 5066.2288,1241 C 5066.2288,1241 4992.2288,1241 4955.2288,1279 C 4955.2288,1279 4915.2288,1317 4915.2288,1374 L 4915.2288,1389 C 4915.2288,1389 4915.2288,1451 4958.2288,1486 C 4958.2288,1486 4997.2288,1519 5065.2288,1519 C 5065.2288,1519 5133.2288,1519 5172.2288,1486 C 5172.2288,1486 5215.2288,1451 5215.2288,1389 L 5215.2288,1374 C 5215.2288,1374 5215.2288,1317 5175.2288,1279 C 5175.2288,1279 5139.2288,1241 5066.2288,1241 z M 5065.2288,1281 C 5065.2288,1281 5109.2288,1281 5119.2288,1303 C 5119.2288,1303 5131.2288,1330 5131.2288,1377 L 5131.2288,1385 C 5131.2288,1385 5131.2288,1433 5116.2288,1459 C 5116.2288,1459 5101.2288,1482 5065.2288,1482 C 5065.2288,1482 5023.2288,1482 5011.2288,1460 C 5011.2288,1460 4998.2288,1442 4998.2288,1385 L 4998.2288,1377 C 4998.2288,1377 4998.2288,1323 5012.2288,1302 C 5012.2288,1302 5023.2288,1281 5065.2288,1281 z"
|
||||
id="path8630"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 5500.2288,357 L 5488.2288,357 L 5470.2288,357 C 5470.2288,357 5449.2288,357 5440.2288,351 L 5402.2288,351 L 5370.2288,360 L 5340.2288,372 C 5340.2288,372 5334.2288,373 5331.2288,381 C 5331.2288,381 5327.2288,391 5333.2288,402 C 5333.2288,402 5340.2288,411 5352.2288,414 L 5390.2288,434 L 5399.2288,443 L 5452.2288,467 L 5478.2288,473 L 5514.2288,473 L 5526.2288,478 C 5526.2288,478 5532.2288,481 5537.2288,478 C 5537.2288,478 5544.2288,473 5556.2288,449 L 5564.2288,440 L 5564.2288,428 L 5565.2288,414 L 5552.2288,402 L 5544.2288,382 C 5544.2288,382 5531.2288,378 5525.2288,375 L 5513.2288,363 L 5500.2288,357"
|
||||
id="path8632"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 5354.2288,508 L 5293.2288,518 L 5292.2288,525 C 5292.2288,525 5289.2288,531 5292.2288,538 L 5302.2288,558 L 5317.2288,577 L 5345.2288,600 C 5345.2288,600 5352.2288,608 5366.2288,611 L 5396.2288,612 L 5422.2288,617 C 5422.2288,617 5434.2288,618 5455.2288,630 L 5476.2288,627 L 5491.2288,627 L 5506.2288,620 C 5506.2288,620 5522.2288,617 5523.2288,615 C 5523.2288,615 5531.2288,612 5538.2288,599 L 5547.2288,576 L 5547.2288,567 L 5549.2288,559 C 5549.2288,559 5550.2288,553 5558.2288,550 L 5568.2288,544 L 5568.2288,523 C 5568.2288,523 5568.2288,520 5553.2288,517 L 5534.2288,515 L 5513.2288,518 L 5455.2288,505 L 5423.2288,503 L 5354.2288,508"
|
||||
id="path8634"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 5213.2288,759 L 5222.2288,800 C 5222.2288,800 5218.2288,824 5221.2288,845 L 5222.2288,893 L 5221.2288,928 L 5237.2288,995 L 5240.2288,1002 L 5240.2288,1027 C 5240.2288,1027 5243.2288,1040 5254.2288,1045 L 5272.2288,1049 L 5296.2288,1060 L 5304.2288,1070 C 5304.2288,1070 5308.2288,1076 5314.2288,1075 C 5314.2288,1075 5319.2288,1073 5319.2288,1060 L 5331.2288,1039 L 5333.2288,1016 L 5339.2288,992 L 5343.2288,975 L 5352.2288,893 L 5348.2288,881 L 5340.2288,869 L 5342.2288,853 L 5342.2288,838 L 5345.2288,836 L 5390.2288,838 L 5407.2288,819 C 5407.2288,819 5432.2288,795 5448.2288,789 L 5455.2288,800 C 5455.2288,800 5461.2288,816 5457.2288,841 L 5423.2288,880 L 5402.2288,901 C 5402.2288,901 5389.2288,912 5386.2288,925 L 5379.2288,930 L 5378.2288,940 L 5376.2288,968 C 5376.2288,968 5376.2288,992 5379.2288,998 L 5392.2288,1007 L 5407.2288,1013 L 5419.2288,1023 L 5467.2288,1027 L 5517.2288,1017 L 5546.2288,1023 L 5584.2288,1022 L 5600.2288,1013 L 5612.2288,1008 L 5623.2288,996 L 5623.2288,980 L 5626.2288,943 L 5612.2288,936 L 5609.2288,934 L 5593.2288,921 L 5591.2288,909 L 5614.2288,848 L 5615.2288,815 C 5615.2288,815 5611.2288,807 5617.2288,791 L 5617.2288,760 L 5600.2288,741 L 5582.2288,714 L 5561.2288,697 L 5552.2288,689 L 5528.2288,683 L 5503.2288,685 L 5490.2288,679 L 5443.2288,711 L 5349.2288,757 L 5337.2288,762 L 5325.2288,765 C 5325.2288,765 5313.2288,765 5308.2288,760 L 5298.2288,748 L 5289.2288,738 L 5283.2288,723 L 5265.2288,715 L 5246.2288,707 C 5246.2288,707 5237.2288,703 5233.2288,694 L 5228.2288,688 L 5221.2288,691 L 5215.2288,729 L 5213.2288,759"
|
||||
id="path8636"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 5314.2288,1515 L 5259.2288,1515 L 5259.2288,1246 L 5319.2288,1246 L 5455.2288,1442 L 5594.2288,1246 L 5647.2288,1246 L 5647.2288,1515 L 5573.2288,1515 L 5573.2288,1361 L 5463.2288,1515 L 5419.2288,1515 L 5314.2288,1361 L 5314.2288,1515"
|
||||
id="path8638"
|
||||
style="fill:#02489d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
</svg>
|
||||
<!-- version: 20090314, original size: 5666.248 1554, border: 3% -->
|
||||
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg2" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 392 293.6"
|
||||
style="enable-background:new 0 0 1000 293.6;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#E60027;}
|
||||
</style>
|
||||
<sodipodi:namedview bordercolor="#000000" borderopacity="0.25" id="namedview1" inkscape:current-layer="svg2" inkscape:cx="500" inkscape:cy="146.85792" inkscape:deskcolor="#d1d1d1" inkscape:pagecheckerboard="0" inkscape:pageopacity="0.0" inkscape:showpageshadow="2" inkscape:window-height="705" inkscape:window-maximized="1" inkscape:window-width="1366" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:zoom="0.732" pagecolor="#ffffff">
|
||||
</sodipodi:namedview>
|
||||
<g id="g23530" transform="matrix(4.6372088,0,0,4.6372088,-2380.96,-486.51923)">
|
||||
<path id="path47920" class="st0" d="M538.7,145.6l5.1-5l-4-4l4-4.1l-5.1-5l-4,4l-2.7-2.7c-4.2-4.2-11.2-4.2-15.4,0
|
||||
c-2.1,2.1-3.2,4.9-3.2,7.7c0,2.8,1,5.6,3.2,7.7c4.2,4.3,11.2,4.3,15.4,0c0.9-0.9,1.8-1.8,2.7-2.7L538.7,145.6z M527,139.2
|
||||
c-1.5,1.5-3.9,1.5-5.3,0c-0.7-0.7-1.1-1.7-1.1-2.6c0-1,0.4-2,1.1-2.7c1.5-1.5,3.9-1.5,5.3,0c1.2,1.2,2.6,2.7,2.6,2.7
|
||||
S528.2,138,527,139.2 M594.2,128.9c-4.2-4.2-11.2-4.2-15.4,0c-1.2,1.2-2.7,2.7-2.7,2.7l-4-4l-5,5l4,4.1l-4,4l5,5l4-4
|
||||
c0,0,1.5,1.4,2.7,2.7c4.3,4.3,11.2,4.3,15.4,0c2.1-2.1,3.2-4.9,3.2-7.7C597.4,133.8,596.3,131,594.2,128.9 M589.1,139.2
|
||||
c-1.5,1.5-3.8,1.5-5.3,0c-1.2-1.2-2.7-2.6-2.7-2.6s1.5-1.5,2.7-2.7c1.5-1.5,3.9-1.5,5.3,0c0.7,0.7,1.1,1.7,1.1,2.7
|
||||
C590.2,137.5,589.9,138.5,589.1,139.2 M560.5,136.6c0,0,11.9-11.9,13-13c4.3-4.3,4.3-11.2,0-15.4c-4.2-4.2-11.1-4.2-15.4,0
|
||||
c-1.7,1.7-2.6,2.7-2.6,2.7s-1-1-2.6-2.7c-4.3-4.2-11.2-4.2-15.4,0c-4.3,4.3-4.3,11.2,0,15.4l13.1,13c0,0-11.9,11.8-13.1,13
|
||||
c-4.3,4.3-4.3,11.2,0,15.4c4.3,4.2,11.2,4.2,15.4,0c1.7-1.7,2.6-2.7,2.6-2.7s1,1,2.6,2.7c4.3,4.2,11.2,4.2,15.4,0
|
||||
c4.3-4.3,4.3-11.2,0-15.4C572.4,148.4,560.5,136.6,560.5,136.6 M542.4,118.5c-1.4-1.5-1.4-3.8,0-5.3c1.5-1.4,3.9-1.4,5.3,0l2.7,2.7
|
||||
l-4,4l5,5.1l4-4l4,4l5-5.1l-4-4c0,0,1.2-1.3,2.7-2.7c1.5-1.4,3.8-1.4,5.3,0c1.5,1.5,1.5,3.8,0,5.3c-1.5,1.5-13.1,13.1-13.1,13.1
|
||||
S543.9,120,542.4,118.5 M568.5,160c-1.5,1.4-3.8,1.4-5.3,0c-1.4-1.4-2.7-2.7-2.7-2.7l4-4l-5-5l-4,4l-4-4l-5,5l4,4
|
||||
c0,0-1.3,1.2-2.7,2.7c-1.4,1.4-3.8,1.4-5.3,0c-1.4-1.5-1.4-3.9,0-5.3c1.5-1.5,13-13.1,13-13.1s11.5,11.6,13.1,13.1
|
||||
C570,156.1,570,158.5,568.5,160"/>
|
||||
<g id="g23504">
|
||||
<path id="path23467" d="M644,149.2c-2.5-0.1-3.3,1.3-3.3,1.3l-0.1-1.2h-2.2v10.3h2.6v-6.3c0-1.3,0.8-2.4,2.3-2.4
|
||||
c1.5,0,2,1.4,2,2.4v6.3h2.4v-6.1C647.6,150.4,645.9,149.2,644,149.2"/>
|
||||
<path id="path23465" d="M621.8,157.1c-0.6,0.8-1.3,1.5-2.2,2c-1,0.6-2.2,0.9-3.5,0.9c-1.9,0-3.5-0.7-4.8-2c-1.3-1.3-2-2.9-2-4.8
|
||||
c0-2,0.7-3.7,2.3-5c1.3-1.2,2.8-1.8,4.5-1.8c1.2,0,2.4,0.3,3.5,0.9c0.9,0.5,1.6,1.2,2.2,2l-2.2,1.6c-0.1-0.4-0.4-0.7-0.6-1
|
||||
c-0.7-0.8-1.7-1.3-2.8-1.3c-1.1,0-2.1,0.5-2.8,1.3c-0.8,0.9-1.1,2-1.1,3.3c0,1.4,0.4,2.5,1.1,3.4c0.7,0.8,1.7,1.3,2.8,1.3
|
||||
c1.1,0,2.1-0.4,2.8-1.3c0.3-0.3,0.5-0.7,0.6-1C619.6,155.5,621.2,156.7,621.8,157.1"/>
|
||||
<path id="path23463" d="M622.7,144.7h2.6v5.8c0,0,0.9-1.3,3-1.3c1.9,0,3.6,1.2,3.6,4.4v6.1h-2.4v-6.3c0-1.1-0.4-2.4-1.9-2.4
|
||||
c-1.5,0-2.2,1.1-2.2,2.4v6.3h-2.6L622.7,144.7z"/>
|
||||
<path id="path47924" d="M657.3,149.4v1.2c-1-1-2.1-1.5-3.4-1.5c-1.4,0-2.7,0.5-3.6,1.5c-1,1-1.5,2.3-1.5,3.8
|
||||
c0,1.6,0.5,2.9,1.5,3.9c1,1,2.2,1.6,3.7,1.6c1.2,0,2.3-0.5,3.3-1.4v1.1h2.5v-10.3H657.3z M656.6,156.9c-0.6,0.6-1.3,0.9-2.3,0.9
|
||||
c-0.9,0-1.6-0.3-2.1-0.9c-0.5-0.6-0.8-1.4-0.8-2.4c0-0.9,0.3-1.7,0.8-2.3c0.6-0.6,1.3-0.9,2.2-0.9c0.9,0,1.6,0.3,2.2,0.9
|
||||
c0.6,0.6,0.9,1.4,0.9,2.3C657.4,155.5,657.1,156.2,656.6,156.9"/>
|
||||
<path id="path23481" class="st0" d="M689,145.5c-0.8,0-1.4,0.6-1.4,1.4c0,0.7,0.5,1.4,1.4,1.4h0c0.9,0,1.4-0.6,1.4-1.4
|
||||
C690.4,146.1,689.9,145.5,689,145.5z M687.8,149.4v10.3h2.4v-10.3H687.8z"/>
|
||||
<path id="path23487" class="st0" d="M635.1,145.5c-0.8,0-1.4,0.6-1.4,1.4c0,0.7,0.5,1.4,1.4,1.4h0c0.9,0,1.4-0.6,1.4-1.4
|
||||
C636.5,146.1,636,145.5,635.1,145.5z M633.9,149.3v10.4h2.4v-10.4H633.9z"/>
|
||||
<path id="path47930" d="M725.7,149.2c-1.5,0-2.6,0.6-3.5,1.6c-0.6-1.1-1.6-1.6-2.7-1.6c-2.2-0.1-3.1,1.3-3.1,1.3l-0.1-1.2H714
|
||||
v10.3h2.6v-6.2c0-1.3,0.7-2.4,2.1-2.4c1.4,0,1.8,1.3,1.8,2.4v6.2h2.4v-6.1v0c0-1.8,0.8-2.6,2-2.6c1.4,0,1.8,1.3,1.8,2.4v6.2h2.4
|
||||
v-6C729,150.5,727.5,149.3,725.7,149.2"/>
|
||||
<path id="path23475" d="M701.9,157.7c-0.4,0.7-1,1.2-1.7,1.6c-0.8,0.5-1.8,0.7-2.8,0.7c-1.5,0-2.8-0.6-3.8-1.6
|
||||
c-1.1-1-1.6-2.3-1.6-3.8c0-1.6,0.6-2.9,1.8-4c1-0.9,2.3-1.4,3.6-1.4c0.9,0,1.9,0.2,2.7,0.7c0.7,0.4,1.3,0.9,1.7,1.5l-1.9,1.4
|
||||
c-0.1-0.3-0.3-0.5-0.5-0.7c-0.5-0.6-1.2-1-2.1-1c-0.8,0-1.5,0.3-2,1c-0.6,0.6-0.8,1.5-0.8,2.5c0,1,0.3,1.9,0.8,2.5
|
||||
c0.5,0.6,1.2,0.9,2,0.9c0.8,0,1.5-0.3,2-1c0.2-0.2,0.4-0.5,0.5-0.8L701.9,157.7z"/>
|
||||
<path id="path23473" inkscape:label="path23473" d="M668.9,159.8c2.5,0,3.3-1.3,3.3-1.3l0.1,1.2h2.2v-10.3h-2.6v6.3
|
||||
c0,1.3-0.7,2.4-2.2,2.4c-1.5,0-2.1-1.4-2.1-2.4v-6.3H665v6C665,158.6,667,159.8,668.9,159.8"/>
|
||||
<path id="path23471" d="M682.3,149.2c-2.5,0-3.3,1.4-3.3,1.4l-0.1-1.2h-2.3v10.3h2.6v-6.2c0-1.4,0.8-2.4,2.2-2.4
|
||||
c1.5,0,2,1.3,2,2.4v6.2h2.4v-6C685.8,150.5,684.1,149.2,682.3,149.2"/>
|
||||
<path id="path47936" d="M707.4,149.1c1,0,1.9,0.2,2.7,0.7c0.9,0.5,1.5,1.2,2,1.9c0.5,0.8,0.7,1.8,0.7,2.7c0,1-0.3,1.9-0.7,2.7
|
||||
c-0.5,0.8-1.1,1.5-1.9,2c-0.8,0.5-1.8,0.7-2.7,0.7c-1.5,0-2.7-0.5-3.8-1.6c-1.1-1-1.6-2.3-1.6-3.8c0-1.6,0.6-2.9,1.8-4
|
||||
C704.9,149.6,706,149.1,707.4,149.1 M707.5,150.9c-0.8,0-1.5,0.3-2,1c-0.6,0.7-0.8,1.6-0.8,2.6c0,1.1,0.3,2,0.8,2.7
|
||||
c0.6,0.6,1.2,1,2.1,1c0.8,0,1.5-0.3,2.1-1c0.6-0.7,0.9-1.6,0.9-2.6c0-1-0.3-1.9-0.8-2.6C709.1,151.2,708.4,150.9,707.5,150.9"/>
|
||||
</g>
|
||||
<g id="g23510">
|
||||
<path id="path47940" d="M695.1,113.2l-3.6,5.6h-2.6l2.1-1.5c-1.8-2.5-3.4-4.1-3.4-4.1l-2,1.2l2.6,4.4H685v2.5h4.2v3.9H685v2.6h3.7
|
||||
l-4.4,11.3h1.9c1,0,2-0.7,2.4-1.6l2.7-6.8l3.7,8.4h3.7l-5.1-11.4h4.4v-2.6h-4.9v-3.9h4.9v-2.5h-3.2l2.9-3.7L695.1,113.2z
|
||||
M672.3,113.7v2.6h7.5v3.9h-3.1v-2.6h-3.4v15.7l-1.5,0.5v2.8l8-2.3v5.2h1.4c1.1,0,1.8-0.7,2.2-2.1v-4.1l1.7-0.5V130l-1.7,0.5
|
||||
v-14.2c0-1.6-1-2.6-2.2-2.6H672.3z M676.7,122.7h3.1v3.1h-3.1V122.7z M676.7,128.4h3.1v3.1l-3.1,0.9V128.4z"/>
|
||||
<path id="path47948" d="M633.2,116.7L633.2,116.7h-8.3v-3.6h-4.3v3.6h-5.4v2.6h5.4v10.3h-6.7v-12.9h-4.3v15.4h11.1v7.3h0.7
|
||||
c2.5-0.1,3.6-1.2,3.7-3v-4.3h7.3c2,0,3.6-1.5,3.7-3.6v-9.2C635.9,117.9,634.7,116.7,633.2,116.7 M631.6,128L631.6,128
|
||||
c0,0.9-0.7,1.6-1.6,1.6h0h-5v-10.3h6.7V128z"/>
|
||||
<path id="path47952" d="M641.1,114v25h22.4c2,0,3.6-1.6,3.6-3.7v-18.6c0-1.5-1.2-2.7-2.8-2.7h-17.9v2.5h16.7v18.4
|
||||
c0,0.9-0.7,1.6-1.6,1.6h-16.4V114H641.1z M646.5,119v2.5h5.3v3.1h-5.3v2.6h5.3v4.2h-5.4v2.5h15.4v-2.5h-1.9l1.3-1.6
|
||||
c-1.7-1.5-3-2.3-3-2.3l-1.5,1.5l2.2,2.4h-3v-4.2h5.9v-2.6h-5.9v-3.1h5.9V119H646.5z"/>
|
||||
<path id="path47960" d="M710,114.2v2.5h13.1l-4.3,2.6c-1.6-1.1-3.5-1.9-3.5-1.9L714,119l1.3,1.2h-5.2v15.2h1
|
||||
c1.1,0,2.4-0.8,2.4-2.3V131h4v4.4h1.1c1.3,0,2.3-0.7,2.3-2.3V131h4v1c0,0.5-0.6,0.8-1,0.8h-1.5l0.7,2.6h2.5c1.7,0,3.1-1.3,3.1-3.1
|
||||
v-10c0-1.2-1-2.1-2.1-2.1h-4l5.3-3.2v-2.9H710z M704.7,114.7l-2,1.4l3.4,4.5l2.6-1.9C706.7,116.1,704.7,114.7,704.7,114.7z
|
||||
M702.4,121.8v2.5h1.9v11.6c0,2,1.7,3.2,3.1,3.2h21.7v-2.5h-19.3c-0.9,0-1.7-0.8-1.7-1.7v-13.1H702.4z M713.7,122.7h4v1.7h-4
|
||||
V122.7z M721.1,122.7h4v1.7h-4V122.7z M713.7,126.9h4v1.7h-4V126.9z M721.1,126.9h4v1.7h-4V126.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,21 @@
|
||||
/** 前端自己的构建信息(构建时由 Vite 写进来,见 web/build-info.mjs)。 */
|
||||
declare const __APP_VERSION__: string
|
||||
declare const __APP_TAG__: string
|
||||
declare const __APP_COMMIT__: string
|
||||
declare const __APP_BUILT_AT__: string
|
||||
|
||||
export const frontendBuild = {
|
||||
version: __APP_VERSION__,
|
||||
tag: __APP_TAG__,
|
||||
commit: __APP_COMMIT__,
|
||||
builtAt: __APP_BUILT_AT__,
|
||||
}
|
||||
|
||||
/** 构建时间显示成本地时间「2026-10-06 20:30」;没有(开发服务器)就是空。 */
|
||||
export function fmtBuiltAt(iso: string | null | undefined): string {
|
||||
if (!iso) return ''
|
||||
const d = new Date(iso)
|
||||
if (Number.isNaN(d.getTime())) return iso
|
||||
const p = (n: number) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
||||
}
|
||||
@@ -143,6 +143,7 @@ export function translateCode(code: string): string | undefined {
|
||||
|
||||
const SETTINGS: Record<string, string> = {
|
||||
'site.public': '匿名访客能不能读展示页的数据(关闭时只有登录的管理员能看)',
|
||||
'site.show_build': '展示页底部和节点卡片上显示后端、前端、Agent、Probe 的版本、tag 和构建时间',
|
||||
'retention.raw_days': '持续任务的原始数据块保留多少天',
|
||||
'retention.test_raw_days': '按需测试的原始数据块保留多少天',
|
||||
'retention.metric_10s_hours': '10 秒精度的指标保留多少小时',
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { api } from './api/client'
|
||||
import type { NodeInfoDto, Page } from './api/types'
|
||||
import { useQuery } from './hooks'
|
||||
import { OsIcon } from './osIcon'
|
||||
import mobileIcon from './assets/isp/mobile.svg?url'
|
||||
import telecomIcon from './assets/isp/telecom.svg?url'
|
||||
import unicomIcon from './assets/isp/unicom.svg?url'
|
||||
|
||||
/**
|
||||
* 节点(Agent、Probe)的展示信息:国旗、运营商、系统和架构。
|
||||
* 后端只给国家代码、运营商名、系统名和架构——没有 IP,所以这里也画不出 IP。
|
||||
*/
|
||||
|
||||
/** 所有国旗的 SVG,按需加载:用到哪个国家才会下载哪个文件。 */
|
||||
const flagLoaders = import.meta.glob<string>('../node_modules/flag-icons/flags/4x3/*.svg', { query: '?url', import: 'default' })
|
||||
|
||||
const regionNames = typeof Intl !== 'undefined' && 'DisplayNames' in Intl ? new Intl.DisplayNames(['zh-CN'], { type: 'region' }) : undefined
|
||||
|
||||
export function regionName(code: string): string {
|
||||
try {
|
||||
return regionNames?.of(code.toUpperCase()) ?? code
|
||||
} catch {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
export function Flag({ code }: { code: string }) {
|
||||
const [url, setUrl] = useState<string>()
|
||||
useEffect(() => {
|
||||
const load = flagLoaders[`../node_modules/flag-icons/flags/4x3/${code.toLowerCase()}.svg`]
|
||||
if (!load) {
|
||||
setUrl(undefined)
|
||||
return
|
||||
}
|
||||
let alive = true
|
||||
void load().then((u) => alive && setUrl(u))
|
||||
return () => {
|
||||
alive = false
|
||||
}
|
||||
}, [code])
|
||||
const name = regionName(code)
|
||||
if (!url) return <span className="flag ph" title={name} />
|
||||
return <img className="flag" src={url} alt={name} title={name} />
|
||||
}
|
||||
|
||||
/**
|
||||
* 运营商的小图标:三大运营商用它们各自的标志(只取标志,不带文字),广电没有现成的文件,用品牌色方块加一个字代替;
|
||||
* 别的运营商没有图标,只显示名字。这些标志是各公司的商标,只用来标明「这是哪家运营商」。
|
||||
*/
|
||||
const ISP_ICON: Record<string, string> = { mobile: mobileIcon, telecom: telecomIcon, unicom: unicomIcon }
|
||||
const ISP_GLYPH: Record<string, string> = { broadnet: '广' }
|
||||
|
||||
export function IspIcon({ ispKey, name }: { ispKey: string; name?: string | null }) {
|
||||
const icon = ISP_ICON[ispKey]
|
||||
if (icon) return <img className="isp logo" src={icon} alt={name ?? ispKey} title={name ?? ispKey} />
|
||||
const glyph = ISP_GLYPH[ispKey]
|
||||
if (!glyph) return null
|
||||
return (
|
||||
<svg className={`isp isp-${ispKey}`} viewBox="0 0 16 16" role="img" aria-label={name ?? ispKey}>
|
||||
<title>{name ?? ispKey}</title>
|
||||
<rect width="16" height="16" rx="4" />
|
||||
<text x="8" y="12.2" textAnchor="middle">
|
||||
{glyph}
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
/** 节点的展示信息,按节点 id 查:`useNodeInfos().get(id)`。每分钟刷新一次。 */
|
||||
export function useNodeInfos(): Map<string, NodeInfoDto> {
|
||||
const { data } = useQuery(() => api.get<Page<NodeInfoDto>>('/api/public/nodes'), [], 60_000)
|
||||
return useMemo(() => new Map((data?.items ?? []).map((n) => [n.id, n])), [data])
|
||||
}
|
||||
|
||||
/** 系统和架构:「Debian 12 · arm64」。 */
|
||||
export const platformText = (info: NodeInfoDto | undefined) => [info?.os_name, info?.arch].filter(Boolean).join(' · ')
|
||||
|
||||
interface TagProps {
|
||||
info: NodeInfoDto | undefined
|
||||
/** 不写系统和架构,只留国旗和运营商。 */
|
||||
geoOnly?: boolean
|
||||
}
|
||||
|
||||
/** 国旗 + 运营商(图标,或者别的运营商的名字)+ 系统和架构。什么都没有就什么也不画。 */
|
||||
export function NodeTag({ info, geoOnly }: TagProps) {
|
||||
if (!info) return null
|
||||
const platform = geoOnly ? '' : platformText(info)
|
||||
if (!info.country && !info.isp_name && !platform) return null
|
||||
const title = [info.country ? regionName(info.country) : null, info.isp_name, platformText(info)].filter(Boolean).join(' · ')
|
||||
return (
|
||||
<span className="node-tag" title={title}>
|
||||
{info.country ? <Flag code={info.country} /> : null}
|
||||
{info.isp_key ? <IspIcon ispKey={info.isp_key} name={info.isp_name} /> : null}
|
||||
{info.isp_key === 'other' && info.isp_name ? <span className="isp-name">{info.isp_name}</span> : null}
|
||||
{platform ? (
|
||||
<span className="plat">
|
||||
<OsIcon os={info.os} name={info.os_name} />
|
||||
{platform}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
siAlmalinux,
|
||||
siAlpinelinux,
|
||||
siApple,
|
||||
siArchlinux,
|
||||
siCentos,
|
||||
siDebian,
|
||||
siFedora,
|
||||
siFreebsd,
|
||||
siKalilinux,
|
||||
siLinux,
|
||||
siLinuxmint,
|
||||
siNixos,
|
||||
siOpensuse,
|
||||
siOpenwrt,
|
||||
siRaspberrypi,
|
||||
siRedhat,
|
||||
siRockylinux,
|
||||
siUbuntu,
|
||||
} from 'simple-icons'
|
||||
|
||||
interface Glyph {
|
||||
/** 24×24 的图形路径。 */
|
||||
path: string
|
||||
/** 品牌色(6 位十六进制);黑色的图标不填,用文字颜色。 */
|
||||
color?: string
|
||||
}
|
||||
|
||||
const fromSimple = (icon: { path: string; hex: string }, useBrandColor = true): Glyph => ({ path: icon.path, color: useBrandColor ? icon.hex : undefined })
|
||||
|
||||
/** Windows 的四个窗格(simple-icons 没有收,自己画的最简形状)。 */
|
||||
const WINDOWS: Glyph = { path: 'M2 4.6 10.4 3.4v7.8H2zM11.6 3.2 22 1.8v9.4H11.6zM2 12.8h8.4v7.8L2 19.4zM11.6 12.8H22v9.4l-10.4-1.4z', color: '0078D4' }
|
||||
|
||||
/** 按系统名挑图标:先看 Windows、macOS,再按发行版名字找,认不出的 Linux 用企鹅。 */
|
||||
function pickGlyph(os: string | null | undefined, name: string | null | undefined): Glyph | undefined {
|
||||
if (os === 'windows') return WINDOWS
|
||||
if (os === 'macos') return fromSimple(siApple, false)
|
||||
const n = (name ?? '').toLowerCase()
|
||||
if (n.includes('debian')) return fromSimple(siDebian)
|
||||
if (n.includes('ubuntu')) return fromSimple(siUbuntu)
|
||||
if (n.includes('alpine')) return fromSimple(siAlpinelinux)
|
||||
if (n.includes('openwrt')) return fromSimple(siOpenwrt)
|
||||
if (n.includes('raspbian') || n.includes('raspberry')) return fromSimple(siRaspberrypi)
|
||||
if (n.includes('kali')) return fromSimple(siKalilinux)
|
||||
if (n.includes('mint')) return fromSimple(siLinuxmint)
|
||||
if (n.includes('rocky')) return fromSimple(siRockylinux)
|
||||
if (n.includes('alma')) return fromSimple(siAlmalinux, false)
|
||||
if (n.includes('centos')) return fromSimple(siCentos)
|
||||
if (n.includes('fedora')) return fromSimple(siFedora)
|
||||
if (n.includes('red hat') || n.includes('rhel')) return fromSimple(siRedhat)
|
||||
if (n.includes('arch')) return fromSimple(siArchlinux)
|
||||
if (n.includes('suse')) return fromSimple(siOpensuse)
|
||||
if (n.includes('nixos')) return fromSimple(siNixos)
|
||||
if (n.includes('freebsd')) return fromSimple(siFreebsd)
|
||||
if (os === 'linux') return fromSimple(siLinux, false)
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** 系统的小图标(Debian、Ubuntu、Windows、macOS……)。认不出的系统不画。 */
|
||||
export function OsIcon({ os, name }: { os: string | null | undefined; name: string | null | undefined }) {
|
||||
const glyph = pickGlyph(os, name)
|
||||
if (!glyph) return null
|
||||
return (
|
||||
<svg className="os-icon" viewBox="0 0 24 24" role="img" aria-label={name ?? os ?? ''} style={glyph.color ? ({ '--os-c': `#${glyph.color}` } as React.CSSProperties) : undefined}>
|
||||
<title>{name ?? os ?? ''}</title>
|
||||
<path d={glyph.path} className={glyph.color ? 'brand' : 'plain'} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -915,3 +915,95 @@ pre.code {
|
||||
.src-status.bad .st {
|
||||
color: var(--app-bad);
|
||||
}
|
||||
|
||||
/* ── 节点的国旗、运营商图标、系统和架构 ── */
|
||||
.node-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.node-tag .flag {
|
||||
flex: none;
|
||||
width: 18px;
|
||||
height: 13.5px;
|
||||
border-radius: 2px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 0 0 1px var(--app-outline-soft);
|
||||
}
|
||||
|
||||
.node-tag .flag.ph {
|
||||
display: inline-block;
|
||||
background: var(--app-card-inset);
|
||||
}
|
||||
|
||||
.node-tag .isp {
|
||||
flex: none;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
/* 三大运营商的标志:留出一点空,比例保持 */
|
||||
.node-tag .isp.logo {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.node-tag .isp rect {
|
||||
fill: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
.node-tag .isp text {
|
||||
fill: var(--isp-ink);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.node-tag .isp-mobile rect {
|
||||
fill: var(--isp-mobile);
|
||||
}
|
||||
|
||||
.node-tag .isp-telecom rect {
|
||||
fill: var(--isp-telecom);
|
||||
}
|
||||
|
||||
.node-tag .isp-unicom rect {
|
||||
fill: var(--isp-unicom);
|
||||
}
|
||||
|
||||
.node-tag .isp-broadnet rect {
|
||||
fill: var(--isp-broadnet);
|
||||
}
|
||||
|
||||
.node-tag .isp-name,
|
||||
.node-tag .plat {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
color: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
/* 系统的小图标:有品牌色的用品牌色(和文字色混一点,亮暗两个主题里都看得清),没有的跟文字走 */
|
||||
.os-icon {
|
||||
flex: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.os-icon path.brand {
|
||||
fill: color-mix(in srgb, var(--os-c) 72%, var(--app-ink));
|
||||
}
|
||||
|
||||
.os-icon path.plain {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.node-tag .plat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
@@ -3,6 +3,13 @@
|
||||
* 颜色只在这个文件里出现;其他样式一律引用令牌(stylelint 会拦住 hex / rgb / 具名色)。
|
||||
*/
|
||||
:root {
|
||||
/* 运营商小图标的底色(自己画的简化图标,不是官方 Logo) */
|
||||
--isp-mobile: #1c9be0;
|
||||
--isp-telecom: #1459a8;
|
||||
--isp-unicom: #d8261f;
|
||||
--isp-broadnet: #de6a1b;
|
||||
--isp-ink: #ffffff;
|
||||
|
||||
color-scheme: light;
|
||||
|
||||
--app-bg: #f4f2ed;
|
||||
|
||||
Reference in new issue
Block a user