nginx-lens 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
analyzer/duplicates.py CHANGED
@@ -3,15 +3,16 @@ from typing import List, Dict, Any
3
3
 
4
4
  def find_duplicate_directives(tree) -> List[Dict[str, Any]]:
5
5
  """
6
- Находит дублирующиеся директивы внутри одного блока.
7
- Возвращает список: [{block, directive, count}]
6
+ Находит дублирующиеся директивы внутри одного блока (без вложенности).
7
+ Возвращает список: [{block, directive, count, location}]
8
8
  """
9
9
  analyzer = Analyzer(tree)
10
10
  duplicates = []
11
11
  for d, parent in analyzer.walk():
12
12
  if 'directives' in d:
13
+ # Считаем только прямые дочерние директивы (без вложенных блоков)
13
14
  seen = {}
14
- for sub, _ in analyzer.walk(d['directives'], d):
15
+ for sub in d['directives']:
15
16
  if 'directive' in sub:
16
17
  key = (sub['directive'], str(sub.get('args')))
17
18
  seen[key] = seen.get(key, 0) + 1
@@ -21,6 +22,7 @@ def find_duplicate_directives(tree) -> List[Dict[str, Any]]:
21
22
  'block': d,
22
23
  'directive': directive,
23
24
  'args': args,
24
- 'count': count
25
+ 'count': count,
26
+ 'location': d.get('arg')
25
27
  })
26
28
  return duplicates
commands/analyze.py CHANGED
@@ -13,6 +13,34 @@ from analyzer.dead_locations import find_dead_locations
13
13
  app = typer.Typer()
14
14
  console = Console()
15
15
 
16
+ # Карта советов и критичности для issue_type
17
+ ISSUE_META = {
18
+ 'location_conflict': ("Возможное пересечение location. Это не всегда ошибка: порядок и типы location могут быть корректны. Проверьте, что порядок и типы location соответствуют вашим ожиданиям. Если всё ок — игнорируйте предупреждение.", "medium"),
19
+ 'duplicate_directive': ("Оставьте только одну директиву с нужным значением в этом блоке.", "medium"),
20
+ 'empty_block': ("Удалите или заполните пустой блок.", "low"),
21
+ 'proxy_pass_no_scheme': ("Добавьте http:// или https:// в proxy_pass.", "medium"),
22
+ 'autoindex_on': ("Отключите autoindex, если не требуется публикация файлов.", "medium"),
23
+ 'if_block': ("Избегайте if внутри location, используйте map/try_files.", "medium"),
24
+ 'server_tokens_on': ("Отключите server_tokens для безопасности.", "low"),
25
+ 'ssl_missing': ("Укажите путь к SSL-сертификату/ключу.", "high"),
26
+ 'ssl_protocols_weak': ("Отключите устаревшие протоколы TLS.", "high"),
27
+ 'ssl_ciphers_weak': ("Используйте современные шифры.", "high"),
28
+ 'listen_443_no_ssl': ("Добавьте ssl к listen 443.", "high"),
29
+ 'listen_443_no_http2': ("Добавьте http2 к listen 443 для производительности.", "low"),
30
+ 'no_limit_req_conn': ("Добавьте limit_req/limit_conn для защиты от DDoS.", "medium"),
31
+ 'missing_security_header': ("Добавьте security-заголовок.", "medium"),
32
+ 'deprecated': ("Замените устаревшую директиву.", "medium"),
33
+ 'limit_too_small': ("Увеличьте лимит до рекомендуемого значения.", "medium"),
34
+ 'limit_too_large': ("Уменьшите лимит до разумного значения.", "medium"),
35
+ 'unused_variable': ("Удалите неиспользуемую переменную.", "low"),
36
+ 'listen_servername_conflict': ("Измените listen/server_name для устранения конфликта.", "high"),
37
+ 'rewrite_cycle': ("Проверьте rewrite на циклические правила.", "high"),
38
+ 'rewrite_conflict': ("Проверьте порядок и уникальность rewrite.", "medium"),
39
+ 'rewrite_no_flag': ("Добавьте last/break/redirect/permanent к rewrite.", "low"),
40
+ 'dead_location': ("Удалите неиспользуемый location или используйте его.", "low"),
41
+ }
42
+ SEVERITY_COLOR = {"high": "red", "medium": "orange3", "low": "yellow"}
43
+
16
44
  def analyze(config_path: str = typer.Argument(..., help="Путь к nginx.conf")):
17
45
  """
18
46
  Анализирует конфигурацию Nginx на типовые проблемы и best practices.
@@ -43,30 +71,58 @@ def analyze(config_path: str = typer.Argument(..., help="Путь к nginx.conf"
43
71
  table = Table(show_header=True, header_style="bold blue")
44
72
  table.add_column("issue_type")
45
73
  table.add_column("issue_description")
74
+ table.add_column("solution")
75
+
76
+ def add_row(issue_type, desc):
77
+ solution, severity = ISSUE_META.get(issue_type, ("", "low"))
78
+ color = SEVERITY_COLOR.get(severity, "yellow")
79
+ table.add_row(f"[{color}]{issue_type}[/{color}]", desc, f"[{color}]{solution}[/{color}]")
46
80
 
47
81
  for c in conflicts:
48
- table.add_row("location_conflict", f"server: {c['server'].get('arg', '')} location: {c['location1']} ↔ {c['location2']}")
82
+ add_row("location_conflict", f"server: {c['server'].get('arg', '')} location: {c['location1']} ↔ {c['location2']}")
49
83
  for d in dups:
50
- table.add_row("duplicate_directive", f"{d['directive']} ({d['args']}) — {d['count']} раз в блоке {d['block'].get('block', d['block'])}")
84
+ loc = d.get('location')
85
+ add_row("duplicate_directive", f"{d['directive']} ({d['args']}) — {d['count']} раз в блоке {d['block'].get('block', d['block'])}{' location: '+str(loc) if loc else ''}")
51
86
  for e in empties:
52
- table.add_row("empty_block", f"{e['block']} {e['arg'] or ''}")
87
+ add_row("empty_block", f"{e['block']} {e['arg'] or ''}")
53
88
  for w in warnings:
54
- if w['type'] == 'proxy_pass_no_scheme':
55
- table.add_row("proxy_pass_no_scheme", f"proxy_pass без схемы: {w['value']}")
56
- elif w['type'] == 'autoindex_on':
57
- table.add_row("autoindex_on", f"autoindex on в блоке {w['context'].get('block','')}")
58
- elif w['type'] == 'if_block':
59
- table.add_row("if_block", f"директива if внутри блока {w['context'].get('block','')}")
60
- elif w['type'] == 'server_tokens_on':
61
- table.add_row("server_tokens_on", f"server_tokens on в блоке {w['context'].get('block','')}")
89
+ t = w['type']
90
+ if t == 'proxy_pass_no_scheme':
91
+ add_row(t, f"proxy_pass без схемы: {w['value']}")
92
+ elif t == 'autoindex_on':
93
+ add_row(t, f"autoindex on в блоке {w['context'].get('block','')}")
94
+ elif t == 'if_block':
95
+ add_row(t, f"директива if внутри блока {w['context'].get('block','')}")
96
+ elif t == 'server_tokens_on':
97
+ add_row(t, f"server_tokens on в блоке {w['context'].get('block','')}")
98
+ elif t == 'ssl_missing':
99
+ add_row(t, f"{w['directive']} не указан")
100
+ elif t == 'ssl_protocols_weak':
101
+ add_row(t, f"ssl_protocols содержит устаревшие протоколы: {w['value']}")
102
+ elif t == 'ssl_ciphers_weak':
103
+ add_row(t, f"ssl_ciphers содержит слабые шифры: {w['value']}")
104
+ elif t == 'listen_443_no_ssl':
105
+ add_row(t, f"listen без ssl: {w['value']}")
106
+ elif t == 'listen_443_no_http2':
107
+ add_row(t, f"listen 443 без http2: {w['value']}")
108
+ elif t == 'no_limit_req_conn':
109
+ add_row(t, f"server без limit_req/limit_conn")
110
+ elif t == 'missing_security_header':
111
+ add_row(t, f"отсутствует security header: {w['value']}")
112
+ elif t == 'deprecated':
113
+ add_row(t, f"устаревшая директива: {w['directive']} — {w['value']}")
114
+ elif t == 'limit_too_small':
115
+ add_row(t, f"слишком маленькое значение: {w['directive']} = {w['value']}")
116
+ elif t == 'limit_too_large':
117
+ add_row(t, f"слишком большое значение: {w['directive']} = {w['value']}")
62
118
  for v in unused_vars:
63
- table.add_row("unused_variable", v['name'])
119
+ add_row("unused_variable", v['name'])
64
120
  for c in listen_conflicts:
65
- table.add_row("listen_servername_conflict", f"server1: {c['server1'].get('arg','')} server2: {c['server2'].get('arg','')} listen: {','.join(c['listen'])} server_name: {','.join(c['server_name'])}")
121
+ add_row("listen_servername_conflict", f"server1: {c['server1'].get('arg','')} server2: {c['server2'].get('arg','')} listen: {','.join(c['listen'])} server_name: {','.join(c['server_name'])}")
66
122
  for r in rewrite_issues:
67
- table.add_row(r['type'], r['value'])
123
+ add_row(r['type'], r['value'])
68
124
  for l in dead_locations:
69
- table.add_row("dead_location", f"server: {l['server'].get('arg','')} location: {l['location'].get('arg','')}")
125
+ add_row("dead_location", f"server: {l['server'].get('arg','')} location: {l['location'].get('arg','')}")
70
126
 
71
127
  if table.row_count == 0:
72
128
  console.print("[green]Проблем не найдено[/green]")
commands/cli.py CHANGED
@@ -7,8 +7,8 @@ from commands.diff import diff
7
7
  from commands.route import route
8
8
  from commands.include import include_tree
9
9
  from commands.graph import graph
10
- from commands.logs import app as logs_app
11
- from commands.syntax import app as syntax_app
10
+ from commands.logs import logs
11
+ from commands.syntax import syntax
12
12
 
13
13
  app = typer.Typer(help="nginx-lens — анализ и диагностика конфигураций Nginx")
14
14
  console = Console()
@@ -20,8 +20,8 @@ app.command()(diff)
20
20
  app.command()(route)
21
21
  app.command()(include_tree)
22
22
  app.command()(graph)
23
- app.add_typer(logs_app, name="logs")
24
- app.add_typer(syntax_app, name="syntax")
23
+ app.command()(logs)
24
+ app.command()(syntax)
25
25
 
26
26
  if __name__ == "__main__":
27
27
  app()
commands/graph.py CHANGED
@@ -2,25 +2,74 @@ import typer
2
2
  from rich.console import Console
3
3
  from parser.nginx_parser import parse_nginx_config
4
4
  from exporter.graph import tree_to_dot, tree_to_mermaid
5
+ from rich.text import Text
5
6
 
6
7
  app = typer.Typer()
7
8
  console = Console()
8
9
 
9
10
  def graph(
10
- config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
11
- format: str = typer.Option("dot", help="Формат: dot или mermaid")
11
+ config_path: str = typer.Argument(..., help="Путь к nginx.conf")
12
12
  ):
13
13
  """
14
- Генерирует схему маршрутизации nginx (dot/mermaid).
14
+ Показывает все возможные маршруты nginx в виде цепочек server → location → proxy_pass → upstream → server.
15
15
 
16
16
  Пример:
17
- nginx-lens graph /etc/nginx/nginx.conf --format dot
18
- nginx-lens graph /etc/nginx/nginx.conf --format mermaid
17
+ nginx-lens graph /etc/nginx/nginx.conf
19
18
  """
20
19
  tree = parse_nginx_config(config_path)
21
- if format == "dot":
22
- console.print(tree_to_dot(tree.directives))
23
- elif format == "mermaid":
24
- console.print(tree_to_mermaid(tree.directives))
25
- else:
26
- console.print("[red]Неизвестный формат: выберите dot или mermaid[/red]")
20
+ routes = []
21
+ # Для каждого server/location строим маршрут
22
+ def walk(d, chain, upstreams):
23
+ if d.get('block') == 'server':
24
+ srv = d.get('arg','') or '[no arg]'
25
+ for sub in d.get('directives', []):
26
+ walk(sub, chain + [('server', srv)], upstreams)
27
+ elif d.get('block') == 'location':
28
+ loc = d.get('arg','')
29
+ for sub in d.get('directives', []):
30
+ walk(sub, chain + [('location', loc)], upstreams)
31
+ elif d.get('directive') == 'proxy_pass':
32
+ val = d.get('args','')
33
+ # ищем, есть ли такой upstream
34
+ up_name = None
35
+ if val.startswith('http://') or val.startswith('https://'):
36
+ up = val.split('://',1)[1].split('/',1)[0]
37
+ if up in upstreams:
38
+ up_name = up
39
+ if up_name:
40
+ for srv in upstreams[up_name]:
41
+ routes.append(chain + [('proxy_pass', val), ('upstream', up_name), ('upstream_server', srv)])
42
+ else:
43
+ routes.append(chain + [('proxy_pass', val)])
44
+ elif d.get('upstream'):
45
+ # собираем upstream-ы
46
+ upstreams[d['upstream']] = d.get('servers',[])
47
+ # рекурсивно по всем директивам
48
+ for sub in d.get('directives', []):
49
+ walk(sub, chain, upstreams)
50
+ # Собираем upstream-ы
51
+ upstreams = {}
52
+ for d in tree.directives:
53
+ if d.get('upstream'):
54
+ upstreams[d['upstream']] = d.get('servers',[])
55
+ # Строим маршруты
56
+ for d in tree.directives:
57
+ walk(d, [], upstreams)
58
+ if not routes:
59
+ console.print("[yellow]Не найдено ни одного маршрута[/yellow]")
60
+ return
61
+ # Красивый вывод
62
+ for route in routes:
63
+ t = Text()
64
+ for i, (typ, val) in enumerate(route):
65
+ if typ == 'server':
66
+ t.append(f"server: {val}", style="bold blue")
67
+ elif typ == 'location':
68
+ t.append(f" -> location: {val}", style="yellow")
69
+ elif typ == 'proxy_pass':
70
+ t.append(f" -> proxy_pass: {val}", style="green")
71
+ elif typ == 'upstream':
72
+ t.append(f" -> upstream: {val}", style="magenta")
73
+ elif typ == 'upstream_server':
74
+ t.append(f" -> server: {val}", style="grey50")
75
+ console.print(t)
commands/logs.py CHANGED
@@ -9,7 +9,6 @@ console = Console()
9
9
 
10
10
  log_line_re = re.compile(r'(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) [^\"]+" (?P<status>\d{3})')
11
11
 
12
- @app.command()
13
12
  def logs(
14
13
  log_path: str = typer.Argument(..., help="Путь к access.log или error.log"),
15
14
  top: int = typer.Option(10, help="Сколько топ-значений выводить")
commands/route.py CHANGED
@@ -3,12 +3,14 @@ from rich.console import Console
3
3
  from rich.panel import Panel
4
4
  from analyzer.route import find_route
5
5
  from parser.nginx_parser import parse_nginx_config
6
+ import glob
7
+ import os
6
8
 
7
9
  app = typer.Typer()
8
10
  console = Console()
9
11
 
10
12
  def route(
11
- config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
13
+ config_path: str = typer.Argument(None, help="Путь к nginx.conf (если не указан — поиск по всем .conf в /etc/nginx)", show_default=False),
12
14
  url: str = typer.Argument(..., help="URL для маршрутизации (например, http://host/path)")
13
15
  ):
14
16
  """
@@ -16,18 +18,32 @@ def route(
16
18
 
17
19
  Пример:
18
20
  nginx-lens route /etc/nginx/nginx.conf http://example.com/api/v1
21
+ nginx-lens route http://example.com/api/v1
19
22
  """
20
- tree = parse_nginx_config(config_path)
21
- res = find_route(tree, url)
22
- if not res:
23
- console.print(Panel(f"Не найден подходящий server для {url}", style="red"))
24
- return
25
- server = res['server']
26
- location = res['location']
27
- proxy_pass = res['proxy_pass']
28
- text = f"[bold]Server:[/bold] {server.get('arg','') or '[no arg]'}\n"
29
- if location:
30
- text += f"[bold]Location:[/bold] {location.get('arg','')}\n"
31
- if proxy_pass:
32
- text += f"[bold]proxy_pass:[/bold] {proxy_pass}\n"
33
- console.print(Panel(text, title="Route", style="green"))
23
+ configs = []
24
+ if config_path:
25
+ configs = [config_path]
26
+ else:
27
+ configs = glob.glob("/etc/nginx/**/*.conf", recursive=True)
28
+ if not configs:
29
+ console.print(Panel("Не найдено ни одного .conf файла в /etc/nginx", style="red"))
30
+ return
31
+ for conf in configs:
32
+ try:
33
+ tree = parse_nginx_config(conf)
34
+ except Exception as e:
35
+ continue # пропускаем битые/невалидные
36
+ res = find_route(tree, url)
37
+ if res:
38
+ server = res['server']
39
+ location = res['location']
40
+ proxy_pass = res['proxy_pass']
41
+ text = f"[bold]Config:[/bold] {conf}\n"
42
+ text += f"[bold]Server:[/bold] {server.get('arg','') or '[no arg]'}\n"
43
+ if location:
44
+ text += f"[bold]Location:[/bold] {location.get('arg','')}\n"
45
+ if proxy_pass:
46
+ text += f"[bold]proxy_pass:[/bold] {proxy_pass}\n"
47
+ console.print(Panel(text, title="Route", style="green"))
48
+ return
49
+ console.print(Panel(f"Ни один ваш конфиг в /etc/nginx не обрабатывает этот URL", style="red"))
commands/syntax.py CHANGED
@@ -8,11 +8,10 @@ import re
8
8
  app = typer.Typer(help="Проверка синтаксиса nginx-конфига через nginx -t с подсветкой ошибок.")
9
9
  console = Console()
10
10
 
11
- ERROR_RE = re.compile(r'in (.+?):(\d+)')
11
+ ERRORS_RE = re.compile(r'in (.+?):(\d+)(?:\s*\n)?(.+?)(?=\nin |$)', re.DOTALL)
12
12
 
13
- @app.command()
14
13
  def syntax(
15
- config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
14
+ config_path: str = typer.Option(None, "-c", "--config", help="Путь к кастомному nginx.conf"),
16
15
  nginx_path: str = typer.Option("nginx", help="Путь к бинарю nginx (по умолчанию 'nginx')")
17
16
  ):
18
17
  """
@@ -21,26 +20,43 @@ def syntax(
21
20
  В случае ошибки показывает место в виде таблицы с контекстом.
22
21
 
23
22
  Пример:
24
- nginx-lens syntax /etc/nginx/nginx.conf
25
- nginx-lens syntax /etc/nginx/nginx.conf --nginx-path /usr/local/sbin/nginx
23
+ nginx-lens syntax -c ./mynginx.conf
24
+ nginx-lens syntax
26
25
  """
26
+ if not config_path:
27
+ candidates = [
28
+ "/etc/nginx/nginx.conf",
29
+ "/usr/local/etc/nginx/nginx.conf",
30
+ "./nginx.conf"
31
+ ]
32
+ config_path = next((p for p in candidates if os.path.isfile(p)), None)
33
+ if not config_path:
34
+ console.print("[red]Не удалось найти nginx.conf. Укажите путь через -c.[/red]")
35
+ return
27
36
  cmd = [nginx_path, "-t", "-c", os.path.abspath(config_path)]
28
37
  try:
29
38
  result = subprocess.run(cmd, capture_output=True, text=True, check=False)
30
39
  if result.returncode == 0:
31
40
  console.print("[green]Синтаксис nginx-конфига корректен[/green]")
41
+ return
32
42
  else:
33
43
  console.print("[red]Ошибка синтаксиса![/red]")
34
44
  console.print(result.stdout)
35
45
  console.print(result.stderr)
36
- # Парсим ошибку
46
+ # Парсим все ошибки
37
47
  err = result.stderr or result.stdout
38
- m = ERROR_RE.search(err)
39
- if m:
40
- file, line = m.group(1), int(m.group(2))
41
- msg = err.strip().split('\n')[-1]
48
+ errors = list(ERRORS_RE.finditer(err))
49
+ if not errors:
50
+ console.print("[red]Не удалось определить файл и строку ошибки[/red]")
51
+ return
52
+ table = Table(title="Ошибки синтаксиса", show_header=True, header_style="bold red")
53
+ table.add_column("config_file")
54
+ table.add_column("issue_message")
55
+ table.add_column("context")
56
+ for m in errors:
57
+ file, line, msg = m.group(1), int(m.group(2)), m.group(3).strip().split('\n')[0]
42
58
  # Читаем контекст
43
- context = []
59
+ context_lines = []
44
60
  try:
45
61
  with open(file) as f:
46
62
  lines = f.readlines()
@@ -48,16 +64,10 @@ def syntax(
48
64
  end = min(len(lines), line+2)
49
65
  for i in range(start, end):
50
66
  mark = "->" if i+1 == line else " "
51
- context.append((str(i+1), mark, lines[i].rstrip()))
67
+ context_lines.append(f"{mark} {lines[i].rstrip()}")
52
68
  except Exception:
53
- context = []
54
- table = Table(title="Ошибка синтаксиса", show_header=True, header_style="bold red")
55
- table.add_column("File")
56
- table.add_column("Line")
57
- table.add_column("Message")
58
- table.add_column("Context")
59
- for ln, mark, code in context:
60
- table.add_row(file, ln, msg if mark == "->" else "", f"{mark} {code}")
61
- console.print(table)
69
+ context_lines = []
70
+ table.add_row(file, msg, '\n'.join(context_lines))
71
+ console.print(table)
62
72
  except FileNotFoundError:
63
73
  console.print(f"[red]Не найден бинарь nginx: {nginx_path}[/red]")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nginx-lens
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: CLI-инструмент для анализа, визуализации и диагностики конфигураций Nginx
5
5
  Author: Daniil Astrouski
6
6
  Author-email: shelovesuastra@gmail.com
@@ -3,7 +3,7 @@ analyzer/base.py,sha256=oGKg78BfMVmuzYafc08oq9p31-jEgYolGjLkUcIdkN8,607
3
3
  analyzer/conflicts.py,sha256=NSNZc8e2x51K41dflSUvuwlDq-rzBXU5ITi6WfxFbfU,2796
4
4
  analyzer/dead_locations.py,sha256=uvMu5qBGTVi0Nn960x3WpRvTljGbQuVFivU4nfe36oY,1435
5
5
  analyzer/diff.py,sha256=idvXnoLzBVUYgKi_s3uDu0v2GNMV3B8aDqTROXcdQdo,1749
6
- analyzer/duplicates.py,sha256=VUjbM19Y_Wwty8GEOu_7nNzOH6mr071p_z1MAkp1kBA,1012
6
+ analyzer/duplicates.py,sha256=jpy_6k-BzWxaXFt2Wb3rlulIXUEzbFe9xYRm7rWR50U,1215
7
7
  analyzer/empty_blocks.py,sha256=7Zu4-5I5PS3bjhH0Ppq1CvM7rMTeRIc4fHx5n5vkMIw,517
8
8
  analyzer/include.py,sha256=FhKR4VsogLknykjLD2N8jX9OtwxZcWik5oPpvp-_luE,2465
9
9
  analyzer/rewrite.py,sha256=-jSLLG1jqmGU-dXWvU6NHCW6muB8Lfro6fXX1tDCHCQ,1834
@@ -11,15 +11,15 @@ analyzer/route.py,sha256=2xxQooQEsfn10tzGCZUoP32T0OnTMnPB6qRgBR6not8,2345
11
11
  analyzer/unused.py,sha256=Ixzv0bPsw9IafblVwLiAOgugdg2dGu1MJDtuoqzPZiY,1066
12
12
  analyzer/warnings.py,sha256=zC36QMvegA2eQPvZ-P1eysrX_kXHx5A1MUKHKKNvG5c,5784
13
13
  commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- commands/analyze.py,sha256=ZpChHk6QXut3S1PvslcX_4IeqdaaXtUXX-ogkB-a6GQ,3618
15
- commands/cli.py,sha256=0uzz71nMXTGlY1odZkEGwtVOjf7uq7762qRLXQaQURo,780
14
+ commands/analyze.py,sha256=QmsyeIlGqvhuRqlUFpPrzg90X5yDVJ3BSIGPhkCHYgI,8026
15
+ commands/cli.py,sha256=9HwDJ-po5al0ceb4Wkyw5F2wzqxbJTo0CbHQ2AQ8obo,722
16
16
  commands/diff.py,sha256=VeZsUu1BYrDubDFrmM5xC4DUWZvWUjFRHfddXm3-I3c,1373
17
- commands/graph.py,sha256=xEDMAOZ9Z5gaOGqLhSIGeRlDvRfSborWwUVjk-XLCsk,930
17
+ commands/graph.py,sha256=MkPasTYSnpG4i3ge_l36t0YUawE3e71v4RWZF9BlJ8A,3084
18
18
  commands/health.py,sha256=d2bBui0qauQtO4Ll9cjniKR1Y5dYJBQzG9CECDnsUQ4,1365
19
19
  commands/include.py,sha256=zPqJpbbSU_6S3L4ntFncPmyFWba8smvdCRog_SFuAFI,1907
20
- commands/logs.py,sha256=NkBJAfKGXvZQDEjHwqdfYikKFMyCrEtjfDeD_MJ9ycg,3435
21
- commands/route.py,sha256=Rnm-zjIbos0NJjUZ0gRs27dCmUfF1tKWKVzdcFcl9hA,1224
22
- commands/syntax.py,sha256=TM9uOZ7MsxF78iJlajybWzouJgH2YeAbUXo8i3GxPVo,2624
20
+ commands/logs.py,sha256=8tnGsgNy_B97S3O0D_6bvOVfNvAyqeUNotlDOdjltgw,3420
21
+ commands/route.py,sha256=MpSI88lqiqkjwA4q-P7gUXmkDicJXYonTwFVC6fNs44,2012
22
+ commands/syntax.py,sha256=iy5JHtqLWs4OtNYuM1xDESlfJ9tOJ4RdNi1KDTfEO1k,3147
23
23
  commands/tree.py,sha256=NEhNU66_e0JCsD4xh4315TM-xwo8NkPwc00lZ4saPzE,1844
24
24
  exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  exporter/graph.py,sha256=WYUrqUgCaK6KihgxAcRHaQn4oMo6b7ybC8yb_36ZIsA,3995
@@ -29,8 +29,8 @@ parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  parser/nginx_parser.py,sha256=JqZ3clNy4Nf-bmbsx_rJUL7EgRoB79b87eEu_isMeqg,3577
30
30
  upstream_checker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  upstream_checker/checker.py,sha256=9-6CMUTN7gXUACP8EwX722QogfujZyV-WWWUeM3a79k,455
32
- nginx_lens-0.1.4.dist-info/METADATA,sha256=MkC1F3wihoxkMKrTJqH0DMrgrZmtTg4L8VqbY85J0jg,476
33
- nginx_lens-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- nginx_lens-0.1.4.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
35
- nginx_lens-0.1.4.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
36
- nginx_lens-0.1.4.dist-info/RECORD,,
32
+ nginx_lens-0.1.5.dist-info/METADATA,sha256=D8emGIWms0maTGCxi5cKe_RMQ9a1veunrpre0tNJUzo,476
33
+ nginx_lens-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ nginx_lens-0.1.5.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
35
+ nginx_lens-0.1.5.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
36
+ nginx_lens-0.1.5.dist-info/RECORD,,