Improve VPN status reporting and recovery
@@ -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 не запускались.
|
||||
@@ -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.')
|
||||
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -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
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 923 B |
|
After Width: | Height: | Size: 995 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 692 B |
|
After Width: | Height: | Size: 703 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 969 B |
|
After Width: | Height: | Size: 966 B |
|
After Width: | Height: | Size: 875 B |
|
After Width: | Height: | Size: 903 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 976 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
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 |