Improve VPN status reporting and recovery

This commit is contained in:
2026-10-13 06:53:52 +03:00
parent fdb15ae462
commit 7a716b4c9c
188 changed files with 6953 additions and 1911 deletions
+19
View File
@@ -0,0 +1,19 @@
# Lemana VPN: единая семья значков
Проект набора для согласования. Значки работающего приложения не заменены.
34 варианта: состояния VPN, ход сессии, варианты времени, действия меню и источники входа. Один контур щита, сетка 24 единицы, основная линия 1,75 единицы; рабочий размер — 16 pt. Цвет всегда дополнен формой или подписью.
- `svg/`: векторные версии для светлой и тёмной темы.
- `png/`: прозрачные превью высотой 96 px для просмотра в разных размерах.
- `icon-sheet-light.png` и `icon-sheet-dark.png`: весь набор крупно и в 16 pt.
- `lemana-vpn-icons-v1.zip`: SVG обеих тем и manifest.
- `draw_icons.py`: исходник геометрии и воспроизводимая сборка (Python 3 + rsvg-convert).
Индикатор времени заполняется по часовой стрелке от верхушки щита. Внутри остаётся нейтральная подложка (14% цвета основного текста), поверх которой снизу вверх растёт заливка (60% текущего цвета). Высота заливки пропорциональна прошедшей доле сессии. Контур нейтральный, пройденная часть зелёная; последний час янтарный, последние 15 минут красные. На полном красном щите песочные часы контрастные, а не красные. Кольцо использует тот же принцип внутренней заливки.
Образцы последних минут показаны для условной восьмичасовой сессии; в приложении доля должна вычисляться по реальным started_at/expires. Неизвестный срок имеет отдельную метку, а не нулевое заполнение. Обычные состояния, статические часы и действия меню не изменены.
Bitwarden обозначен нейтральным значком хранилища, это не копия фирменного логотипа.
Проверены структура SVG, доли заполнения и рендер контактных листов. Приложение/GUI не запускались.
+168
View File
@@ -0,0 +1,168 @@
"""Deterministic review assets for Lemana VPN; does not modify the application."""
from pathlib import Path
import html
import json
import math
import subprocess
import zipfile
ROOT = Path(__file__).resolve().parent
SHIELD = 'M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z'
def curve_length(points):
previous=points[0]; length=0.0
for i in range(1,513):
t=i/512; u=1-t
point=tuple(u**3*points[0][k]+3*u*u*t*points[1][k]+3*u*t*t*points[2][k]+t**3*points[3][k] for k in (0,1))
length+=math.dist(previous,point); previous=point
return length
SHIELD_LENGTH=2*(6.2+curve_length(((12,2),(14.6,3.7),(17.3,4.7),(20,5)))+curve_length(((20,11.2),(20,16.1),(16.7,19.6),(12,22))))
PALETTES = {
'light': dict(bg='#F6F7F8', surface='#ECEFF2', ink='#27333E', muted='#657381', line='#D6DDE3', green='#278055', amber='#A56A13', red='#BE4149'),
'dark': dict(bg='#202429', surface='#2B3037', ink='#E5EAF0', muted='#ACB7C3', line='#444D57', green='#78CAA2', amber='#F0BB68', red='#F58C95'),
}
def path(d, **attrs):
extra = ''.join(f' {key.replace("_", "-")}="{value}"' for key,value in attrs.items())
return f'<path d="{d}"{extra}/>'
def circle(x,y,r,**attrs):
extra = ''.join(f' {key.replace("_", "-")}="{value}"' for key,value in attrs.items())
return f'<circle cx="{x}" cy="{y}" r="{r}"{extra}/>'
def progress_interior(shape,progress,bottom,height,palette,accent):
body=shape(fill=palette['ink'],fill_opacity='.14',stroke='none')
if progress:
level=height*progress
top=bottom-level
# A nested viewport clips the fill level without shared SVG clip-path IDs.
body+=f'<svg x="0" y="{top:.5f}" width="24" height="{level:.5f}" viewBox="0 {top:.5f} 24 {level:.5f}" overflow="hidden">'
body+=shape(fill=accent,fill_opacity='.60',stroke='none')+'</svg>'
return body
MARKS = {
'off': path('M8.7 8.7 L15.3 15.3'),
'connecting': ''.join(circle(x,12,.7,fill='currentColor',stroke='none') for x in (9,12,15)),
'unlock': circle(10,10.5,1.8)+path('M11.4 11.9 L15.4 15.9 M13.5 14 L15 12.5'),
'check': path('M7.8 11.8 L10.8 14.7 L16.2 9.3'),
'reconnect': path('M15.6 10.2 A4 4 0 1 0 15.6 14.1 M15.6 7.3 V10.2 H12.7'),
'network': path('M10 10 H9 A2 2 0 0 0 9 14 H10 M14 10 H15 A2 2 0 0 1 15 14 H14'),
'error': path('M9 9 L15 15 M15 9 L9 15'),
'setup': path('M12 7.8 V12.3')+circle(12,15.5,.65,fill='currentColor',stroke='none'),
'unknown': path('M9.7 9.2 C9.8 6.7 14.6 6.7 14.6 9.4 C14.6 11.3 12 11.3 12 13')+circle(12,15.9,.65,fill='currentColor',stroke='none'),
'expired': path('M9 8 H15 M9 16 H15 M9.7 8 C9.7 10.3 14.3 13.7 14.3 16 M14.3 8 C14.3 10.3 9.7 13.7 9.7 16'),
'no-time': path('M9 12 H15'),
'clock': path('M12 7.5 V12 L15 13.7'),
}
ACTIONS = {
'connect': path('M8 3 V8 M16 3 V8 M6 8 H18 V11 C18 14.3 15.3 17 12 17 C8.7 17 6 14.3 6 11 Z M12 17 V21'),
'disconnect': path('M8 2.5 V6 M16 2.5 V6 M6 6 H18 V8 C18 10.2 15.3 11.5 12 11.5 C8.7 11.5 6 10.2 6 8 Z M7.5 15.5 Q12 19.5 16.5 15.5 M12 17.5 V21.5'),
'cancel': path('M6 6 L18 18 M18 6 L6 18'),
'import': path('M5 13 V20 H19 V13 M12 3 V15 M8 11 L12 15 L16 11'),
'password': circle(8,8,4)+path('M11 11 L20 20 M15 15 L17 13 M18 18 L20 16'),
'copy': path('M9 4 H19 V15 M5 8 H15 V21 H5 Z'),
'logs': path('M6 3 H14 L19 8 V21 H6 Z M14 3 V8 H19 M9 12 H16 M9 16 H16'),
'refresh': path('M19 9 A7.2 7.2 0 0 0 6.8 6.5 L4 9 M4 4 V9 H9 M5 15 A7.2 7.2 0 0 0 17.2 17.5 L20 15 M20 20 V15 H15'),
'quit': path('M11 3 H4 V21 H11 M10 12 H21 M17 8 L21 12 L17 16'),
'manual': '<rect x="3" y="6" width="18" height="12" rx="2"/>'+path('M6.5 10 H7 M11.5 10 H12 M16.5 10 H17 M7 14 H17'),
'auto': path('M13 3 L6 13 H11 L10 21 L18 10 H13 Z'),
'local': '<rect x="5" y="4" width="14" height="12" rx="1.5"/>'+path('M2.5 19 H21.5 L20 21 H4 Z'),
'vault': '<rect x="4.5" y="3" width="15" height="18" rx="2.5"/>'+circle(12,12,3.8)+path('M12 8.2 V10 M15.8 12 H14 M12 15.8 V14 M8.2 12 H10'),
}
ENTRIES=[]
def add(key,label,section,kind,mark='',tone='ink',progress=None):
ENTRIES.append(dict(key=key,label=label,section=section,kind=kind,mark=mark,tone=tone,progress=progress))
for args in [
('vpn-off','Отключён','off','ink'),('vpn-connecting','Подключение','connecting','ink'),
('vpn-unlock','Ожидаем вход','unlock','ink'),('vpn-connected','Подключён','check','green'),
('vpn-reconnect','Переподключение','reconnect','ink'),('vpn-network','Проблема сети','network','amber'),
('vpn-error','Ошибка','error','red'),('vpn-setup','Нужна настройка','setup','amber'),
('vpn-unknown','Статус неизвестен','unknown','ink')]:
add(args[0],args[1],'Состояния VPN','shield',args[2],args[3])
for key,label,progress,tone in [
('time-start','Начало',0,'green'),('time-quarter','25% прошло',.25,'green'),
('time-half','50% прошло',.5,'green'),('time-three-quarter','75% прошло',.75,'green'),
('time-hour','Остался час',.875,'amber'),('time-quarter-hour','Осталось 15 мин',.96875,'red'),
('time-expired','Срок истёк',1,'red')]:
add(key,label,'Время сессии','progress','expired' if progress==1 else '',tone,progress)
add('time-unknown','Срок неизвестен','Время сессии','shield','no-time')
add('format-shield','Щит с прогрессом','Форматы времени','progress','', 'green',.58)
add('format-clock','Щит-часы','Форматы времени','shield','clock','green')
add('format-time','Щит + время','Форматы времени','digital','check','green')
add('format-ring','Кольцо','Форматы времени','ring','', 'green',.58)
for key,label in [('connect','Подключить'),('disconnect','Отключить'),('cancel','Отменить'),('import','Импорт ключа'),('password','Логин и пароль'),('copy','Копировать IP'),('logs','Логи'),('refresh','Обновить статус'),('quit','Выход')]:
add('action-'+key,label,'Действия меню','action',key)
for key,label in [('manual','Вручную'),('auto','Автоматически'),('local','Ключ на Mac'),('vault','Bitwarden')]:
add('source-'+key,label,'Источники и режимы','action',key)
def content(entry,palette):
ink=palette['ink'];accent=palette[entry['tone']]
if entry['kind']=='action': return ACTIONS[entry['mark']]
if entry['kind']=='ring':
body=progress_interior(lambda **attrs:circle(12,12,9,**attrs),entry['progress'],21,18,palette,accent)
return body+circle(12,12,9,stroke=palette['muted'],stroke_opacity='.65')+circle(12,12,9,stroke=accent,stroke_dasharray=f'{entry["progress"]*math.tau*9:.5f} {math.tau*9:.5f}',transform='rotate(-90 12 12)',stroke_linecap='butt')
shape=path(SHIELD,stroke=ink)
if entry['kind']=='progress':
shape=progress_interior(lambda **attrs:path(SHIELD,**attrs),entry['progress'],22,20,palette,accent)
shape+=path(SHIELD,stroke=palette['muted'],stroke_opacity='.68')
if entry['progress']:
shape+=path(SHIELD,stroke=accent,stroke_dasharray=f'{entry["progress"]*SHIELD_LENGTH:.5f} {SHIELD_LENGTH:.5f}',stroke_linecap='butt')
if entry['mark']:
mark_color=ink if entry['kind']=='progress' else accent
shape+=f'<g stroke="{mark_color}" color="{mark_color}">{MARKS[entry["mark"]]}</g>'
if entry['kind']=='digital':
shape+='<text x="29" y="17.2" stroke="none" fill="'+ink+'" font-family="Menlo,monospace" font-size="16.5" font-weight="500">2:18</text>'
return shape
def svg(entry,theme):
p=PALETTES[theme];width=74 if entry['kind']=='digital' else 24
return f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="24" viewBox="0 0 {width} 24" fill="none" stroke="{p["ink"]}" color="{p["ink"]}" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>{html.escape(entry["label"])}</title>'+content(entry,p)+'</svg>'
def text(x,y,value,size=14,color='#657381',weight=400):
return f'<text x="{x}" y="{y}" fill="{color}" font-family="Helvetica Neue,Arial,sans-serif" font-size="{size}" font-weight="{weight}">{html.escape(value)}</text>'
def sheet(theme):
p=PALETTES[theme];width=1120;elements=[]
elements.append(text(42,53,'Lemana VPN',27,p['ink'],600))
elements.append(text(42,82,'Единая семья значков',16,p['muted']))
elements.append(text(824,53,'Щит / 16 pt',14,p['ink'],500))
elements.append(text(824,78,'Контур 1,75 · сетка 24',12,p['muted']))
y=130
for section in dict.fromkeys(e['section'] for e in ENTRIES):
elements.append(text(42,y,section,16,p['ink'],500));y+=24
subset=[e for e in ENTRIES if e['section']==section]
for index,e in enumerate(subset):
col=index%6;row=index//6;x=42+col*174;top=y+row*119
logical_width=74 if e['kind']=='digital' else 24
large_x=x+(160-logical_width*1.5)/2
small_x=x+(160-logical_width*2/3)/2
elements.append(f'<g transform="translate({large_x} {top+4}) scale(1.5)" fill="none" stroke="{p["ink"]}" color="{p["ink"]}" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">'+content(e,p)+'</g>')
elements.append(f'<g transform="translate({small_x} {top+48}) scale(.6666667)" fill="none" stroke="{p["ink"]}" color="{p["ink"]}" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">'+content(e,p)+'</g>')
elements.append(text(x+7,top+91,e['label'],12,p['ink']))
y+=((len(subset)+5)//6)*119+14
elements.append(f'<path d="M42 {y-17} H1078" stroke="{p["line"]}" stroke-width="1"/>')
elements.append(text(42,y+10,'Увеличенный вид + 16 pt. Цвет дополнен формой и подписью.',12,p['muted']))
elements.append(text(42,y+32,'Пример сессии: 8 ч. Контур и внутренняя заливка растут к её концу. Набор пока не установлен.',12,p['muted']))
height=y+65
return f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}"><rect width="{width}" height="{height}" fill="{p["bg"]}"/>'+''.join(elements)+'</svg>'
for theme in PALETTES:
for e in ENTRIES:
source=ROOT/'svg'/f'{e["key"]}-{theme}.svg'
source.write_text(svg(e,theme))
subprocess.run(['rsvg-convert',str(source),'-o',str(ROOT/'png'/f'{e["key"]}-{theme}.png'),'-h','96'],check=True)
source=ROOT/f'icon-sheet-{theme}.svg';source.write_text(sheet(theme))
subprocess.run(['rsvg-convert',str(source),'-o',str(ROOT/f'icon-sheet-{theme}.png'),'-w','2240'],check=True)
(ROOT/'manifest.json').write_text(json.dumps(ENTRIES,ensure_ascii=False,indent=2)+'\n')
with zipfile.ZipFile(ROOT/'lemana-vpn-icons-v1.zip','w',zipfile.ZIP_DEFLATED) as archive:
for file in sorted((ROOT/'svg').glob('*.svg')):
archive.write(file,file.relative_to(ROOT))
archive.write(ROOT/'manifest.json','manifest.json')
print(f'{len(ENTRIES)} icon entries, two themes, SVG assets and preview sheets created.')
Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.
+308
View File
@@ -0,0 +1,308 @@
[
{
"key": "vpn-off",
"label": "Отключён",
"section": "Состояния VPN",
"kind": "shield",
"mark": "off",
"tone": "ink",
"progress": null
},
{
"key": "vpn-connecting",
"label": "Подключение",
"section": "Состояния VPN",
"kind": "shield",
"mark": "connecting",
"tone": "ink",
"progress": null
},
{
"key": "vpn-unlock",
"label": "Ожидаем вход",
"section": "Состояния VPN",
"kind": "shield",
"mark": "unlock",
"tone": "ink",
"progress": null
},
{
"key": "vpn-connected",
"label": "Подключён",
"section": "Состояния VPN",
"kind": "shield",
"mark": "check",
"tone": "green",
"progress": null
},
{
"key": "vpn-reconnect",
"label": "Переподключение",
"section": "Состояния VPN",
"kind": "shield",
"mark": "reconnect",
"tone": "ink",
"progress": null
},
{
"key": "vpn-network",
"label": "Проблема сети",
"section": "Состояния VPN",
"kind": "shield",
"mark": "network",
"tone": "amber",
"progress": null
},
{
"key": "vpn-error",
"label": "Ошибка",
"section": "Состояния VPN",
"kind": "shield",
"mark": "error",
"tone": "red",
"progress": null
},
{
"key": "vpn-setup",
"label": "Нужна настройка",
"section": "Состояния VPN",
"kind": "shield",
"mark": "setup",
"tone": "amber",
"progress": null
},
{
"key": "vpn-unknown",
"label": "Статус неизвестен",
"section": "Состояния VPN",
"kind": "shield",
"mark": "unknown",
"tone": "ink",
"progress": null
},
{
"key": "time-start",
"label": "Начало",
"section": "Время сессии",
"kind": "progress",
"mark": "",
"tone": "green",
"progress": 0
},
{
"key": "time-quarter",
"label": "25% прошло",
"section": "Время сессии",
"kind": "progress",
"mark": "",
"tone": "green",
"progress": 0.25
},
{
"key": "time-half",
"label": "50% прошло",
"section": "Время сессии",
"kind": "progress",
"mark": "",
"tone": "green",
"progress": 0.5
},
{
"key": "time-three-quarter",
"label": "75% прошло",
"section": "Время сессии",
"kind": "progress",
"mark": "",
"tone": "green",
"progress": 0.75
},
{
"key": "time-hour",
"label": "Остался час",
"section": "Время сессии",
"kind": "progress",
"mark": "",
"tone": "amber",
"progress": 0.875
},
{
"key": "time-quarter-hour",
"label": "Осталось 15 мин",
"section": "Время сессии",
"kind": "progress",
"mark": "",
"tone": "red",
"progress": 0.96875
},
{
"key": "time-expired",
"label": "Срок истёк",
"section": "Время сессии",
"kind": "progress",
"mark": "expired",
"tone": "red",
"progress": 1
},
{
"key": "time-unknown",
"label": "Срок неизвестен",
"section": "Время сессии",
"kind": "shield",
"mark": "no-time",
"tone": "ink",
"progress": null
},
{
"key": "format-shield",
"label": "Щит с прогрессом",
"section": "Форматы времени",
"kind": "progress",
"mark": "",
"tone": "green",
"progress": 0.58
},
{
"key": "format-clock",
"label": "Щит-часы",
"section": "Форматы времени",
"kind": "shield",
"mark": "clock",
"tone": "green",
"progress": null
},
{
"key": "format-time",
"label": "Щит + время",
"section": "Форматы времени",
"kind": "digital",
"mark": "check",
"tone": "green",
"progress": null
},
{
"key": "format-ring",
"label": "Кольцо",
"section": "Форматы времени",
"kind": "ring",
"mark": "",
"tone": "green",
"progress": 0.58
},
{
"key": "action-connect",
"label": "Подключить",
"section": "Действия меню",
"kind": "action",
"mark": "connect",
"tone": "ink",
"progress": null
},
{
"key": "action-disconnect",
"label": "Отключить",
"section": "Действия меню",
"kind": "action",
"mark": "disconnect",
"tone": "ink",
"progress": null
},
{
"key": "action-cancel",
"label": "Отменить",
"section": "Действия меню",
"kind": "action",
"mark": "cancel",
"tone": "ink",
"progress": null
},
{
"key": "action-import",
"label": "Импорт ключа",
"section": "Действия меню",
"kind": "action",
"mark": "import",
"tone": "ink",
"progress": null
},
{
"key": "action-password",
"label": "Логин и пароль",
"section": "Действия меню",
"kind": "action",
"mark": "password",
"tone": "ink",
"progress": null
},
{
"key": "action-copy",
"label": "Копировать IP",
"section": "Действия меню",
"kind": "action",
"mark": "copy",
"tone": "ink",
"progress": null
},
{
"key": "action-logs",
"label": "Логи",
"section": "Действия меню",
"kind": "action",
"mark": "logs",
"tone": "ink",
"progress": null
},
{
"key": "action-refresh",
"label": "Обновить статус",
"section": "Действия меню",
"kind": "action",
"mark": "refresh",
"tone": "ink",
"progress": null
},
{
"key": "action-quit",
"label": "Выход",
"section": "Действия меню",
"kind": "action",
"mark": "quit",
"tone": "ink",
"progress": null
},
{
"key": "source-manual",
"label": "Вручную",
"section": "Источники и режимы",
"kind": "action",
"mark": "manual",
"tone": "ink",
"progress": null
},
{
"key": "source-auto",
"label": "Автоматически",
"section": "Источники и режимы",
"kind": "action",
"mark": "auto",
"tone": "ink",
"progress": null
},
{
"key": "source-local",
"label": "Ключ на Mac",
"section": "Источники и режимы",
"kind": "action",
"mark": "local",
"tone": "ink",
"progress": null
},
{
"key": "source-vault",
"label": "Bitwarden",
"section": "Источники и режимы",
"kind": "action",
"mark": "vault",
"tone": "ink",
"progress": null
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Отменить</title><path d="M6 6 L18 18 M18 6 L6 18"/></svg>

After

Width:  |  Height:  |  Size: 267 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Отменить</title><path d="M6 6 L18 18 M18 6 L6 18"/></svg>

After

Width:  |  Height:  |  Size: 267 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Подключить</title><path d="M8 3 V8 M16 3 V8 M6 8 H18 V11 C18 14.3 15.3 17 12 17 C8.7 17 6 14.3 6 11 Z M12 17 V21"/></svg>

After

Width:  |  Height:  |  Size: 333 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Подключить</title><path d="M8 3 V8 M16 3 V8 M6 8 H18 V11 C18 14.3 15.3 17 12 17 C8.7 17 6 14.3 6 11 Z M12 17 V21"/></svg>

After

Width:  |  Height:  |  Size: 333 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Копировать IP</title><path d="M9 4 H19 V15 M5 8 H15 V21 H5 Z"/></svg>

After

Width:  |  Height:  |  Size: 281 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Копировать IP</title><path d="M9 4 H19 V15 M5 8 H15 V21 H5 Z"/></svg>

After

Width:  |  Height:  |  Size: 281 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Отключить</title><path d="M8 2.5 V6 M16 2.5 V6 M6 6 H18 V8 C18 10.2 15.3 11.5 12 11.5 C8.7 11.5 6 10.2 6 8 Z M7.5 15.5 Q12 19.5 16.5 15.5 M12 17.5 V21.5"/></svg>

After

Width:  |  Height:  |  Size: 372 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Отключить</title><path d="M8 2.5 V6 M16 2.5 V6 M6 6 H18 V8 C18 10.2 15.3 11.5 12 11.5 C8.7 11.5 6 10.2 6 8 Z M7.5 15.5 Q12 19.5 16.5 15.5 M12 17.5 V21.5"/></svg>

After

Width:  |  Height:  |  Size: 372 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Импорт ключа</title><path d="M5 13 V20 H19 V13 M12 3 V15 M8 11 L12 15 L16 11"/></svg>

After

Width:  |  Height:  |  Size: 298 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Импорт ключа</title><path d="M5 13 V20 H19 V13 M12 3 V15 M8 11 L12 15 L16 11"/></svg>

After

Width:  |  Height:  |  Size: 298 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Логи</title><path d="M6 3 H14 L19 8 V21 H6 Z M14 3 V8 H19 M9 12 H16 M9 16 H16"/></svg>

After

Width:  |  Height:  |  Size: 292 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Логи</title><path d="M6 3 H14 L19 8 V21 H6 Z M14 3 V8 H19 M9 12 H16 M9 16 H16"/></svg>

After

Width:  |  Height:  |  Size: 292 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Логин и пароль</title><circle cx="8" cy="8" r="4"/><path d="M11 11 L20 20 M15 15 L17 13 M18 18 L20 16"/></svg>

After

Width:  |  Height:  |  Size: 324 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Логин и пароль</title><circle cx="8" cy="8" r="4"/><path d="M11 11 L20 20 M15 15 L17 13 M18 18 L20 16"/></svg>

After

Width:  |  Height:  |  Size: 324 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Выход</title><path d="M11 3 H4 V21 H11 M10 12 H21 M17 8 L21 12 L17 16"/></svg>

After

Width:  |  Height:  |  Size: 285 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Выход</title><path d="M11 3 H4 V21 H11 M10 12 H21 M17 8 L21 12 L17 16"/></svg>

After

Width:  |  Height:  |  Size: 285 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Обновить статус</title><path d="M19 9 A7.2 7.2 0 0 0 6.8 6.5 L4 9 M4 4 V9 H9 M5 15 A7.2 7.2 0 0 0 17.2 17.5 L20 15 M20 20 V15 H15"/></svg>

After

Width:  |  Height:  |  Size: 354 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Обновить статус</title><path d="M19 9 A7.2 7.2 0 0 0 6.8 6.5 L4 9 M4 4 V9 H9 M5 15 A7.2 7.2 0 0 0 17.2 17.5 L20 15 M20 20 V15 H15"/></svg>

After

Width:  |  Height:  |  Size: 354 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Щит-часы</title><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" stroke="#E5EAF0"/><g stroke="#78CAA2" color="#78CAA2"><path d="M12 7.5 V12 L15 13.7"/></g></svg>

After

Width:  |  Height:  |  Size: 443 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Щит-часы</title><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" stroke="#27333E"/><g stroke="#278055" color="#278055"><path d="M12 7.5 V12 L15 13.7"/></g></svg>

After

Width:  |  Height:  |  Size: 443 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Кольцо</title><circle cx="12" cy="12" r="9" fill="#E5EAF0" fill-opacity=".14" stroke="none"/><svg x="0" y="10.56000" width="24" height="10.44000" viewBox="0 10.56000 24 10.44000" overflow="hidden"><circle cx="12" cy="12" r="9" fill="#78CAA2" fill-opacity=".60" stroke="none"/></svg><circle cx="12" cy="12" r="9" stroke="#ACB7C3" stroke-opacity=".65"/><circle cx="12" cy="12" r="9" stroke="#78CAA2" stroke-dasharray="32.79823 56.54867" transform="rotate(-90 12 12)" stroke-linecap="butt"/></svg>

After

Width:  |  Height:  |  Size: 702 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Кольцо</title><circle cx="12" cy="12" r="9" fill="#27333E" fill-opacity=".14" stroke="none"/><svg x="0" y="10.56000" width="24" height="10.44000" viewBox="0 10.56000 24 10.44000" overflow="hidden"><circle cx="12" cy="12" r="9" fill="#278055" fill-opacity=".60" stroke="none"/></svg><circle cx="12" cy="12" r="9" stroke="#657381" stroke-opacity=".65"/><circle cx="12" cy="12" r="9" stroke="#278055" stroke-dasharray="32.79823 56.54867" transform="rotate(-90 12 12)" stroke-linecap="butt"/></svg>

After

Width:  |  Height:  |  Size: 702 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E5EAF0" color="#E5EAF0" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Щит с прогрессом</title><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" fill="#E5EAF0" fill-opacity=".14" stroke="none"/><svg x="0" y="10.40000" width="24" height="11.60000" viewBox="0 10.40000 24 11.60000" overflow="hidden"><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" fill="#78CAA2" fill-opacity=".60" stroke="none"/></svg><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" stroke="#ACB7C3" stroke-opacity=".68"/><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" stroke="#78CAA2" stroke-dasharray="33.66408 58.04152" stroke-linecap="butt"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#27333E" color="#27333E" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><title>Щит с прогрессом</title><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" fill="#27333E" fill-opacity=".14" stroke="none"/><svg x="0" y="10.40000" width="24" height="11.60000" viewBox="0 10.40000 24 11.60000" overflow="hidden"><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" fill="#278055" fill-opacity=".60" stroke="none"/></svg><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" stroke="#657381" stroke-opacity=".68"/><path d="M12 2 C14.6 3.7 17.3 4.7 20 5 V11.2 C20 16.1 16.7 19.6 12 22 C7.3 19.6 4 16.1 4 11.2 V5 C6.7 4.7 9.4 3.7 12 2 Z" stroke="#278055" stroke-dasharray="33.66408 58.04152" stroke-linecap="butt"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

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