v1.0.90: Steps 1-4 — auth.py, dedup, httponly cookie, _op_results TTL
This commit is contained in:
@@ -0,0 +1,82 @@
|
|||||||
|
"""
|
||||||
|
Единый модуль аутентификации и создания HTTP-клиента.
|
||||||
|
|
||||||
|
Раньше _client(), _client_id(), _stand(), _token_info() были продублированы
|
||||||
|
в main.py и api_test.py с идентичным или почти идентичным кодом.
|
||||||
|
Теперь всё здесь — один источник правды.
|
||||||
|
|
||||||
|
Функции:
|
||||||
|
get_token() — токен из cookie или env
|
||||||
|
get_client() — HttpClient с автоопределением стенда
|
||||||
|
get_client_id() — ClientID из JWT (base64, без проверки подписи)
|
||||||
|
get_stand() — "dev"/"test" по токену
|
||||||
|
get_token_info() — {email, company, client_id} из JWT
|
||||||
|
get_token_masked() — маскированный токен (abc...xyz)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
|
||||||
|
from flask import request, current_app
|
||||||
|
from api.http_client import HttpClient, detect_endpoint, stand_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_token():
|
||||||
|
"""Токен: сначала из cookie, потом из env-переменной."""
|
||||||
|
return request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_client():
|
||||||
|
"""HttpClient с автоопределением стенда по токену.
|
||||||
|
|
||||||
|
Использует detect_endpoint() — пробует dev→test стенды.
|
||||||
|
Если автоопределение не сработало — fallback на NUBES_API_ENDPOINT из конфига."""
|
||||||
|
token = get_token()
|
||||||
|
endpoint = detect_endpoint(token) or current_app.config["NUBES_API_ENDPOINT"]
|
||||||
|
return HttpClient(endpoint, token)
|
||||||
|
|
||||||
|
|
||||||
|
def get_client_id():
|
||||||
|
"""Извлечение ClientID из payload JWT-токена (base64url, без проверки подписи)."""
|
||||||
|
token = get_token()
|
||||||
|
try:
|
||||||
|
parts = token.split(".") # header.payload.signature
|
||||||
|
if len(parts) >= 2:
|
||||||
|
payload = base64.urlsafe_b64decode(parts[1] + "==") # padding
|
||||||
|
return json.loads(payload).get("ClientID", "")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_stand():
|
||||||
|
"""dev/test — по токену (detect_endpoint → stand_name)."""
|
||||||
|
token = get_token()
|
||||||
|
endpoint = detect_endpoint(token) or current_app.config["NUBES_API_ENDPOINT"]
|
||||||
|
return stand_name(endpoint)
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_info():
|
||||||
|
"""{email, company, client_id} из JWT — для отображения в топбаре UI."""
|
||||||
|
token = get_token()
|
||||||
|
try:
|
||||||
|
parts = token.split(".")
|
||||||
|
if len(parts) >= 2:
|
||||||
|
payload = base64.urlsafe_b64decode(parts[1] + "==")
|
||||||
|
d = json.loads(payload)
|
||||||
|
return {
|
||||||
|
"email": d.get("email", ""),
|
||||||
|
"company": d.get("company_name", ""),
|
||||||
|
"client_id": d.get("ClientID", ""),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_masked():
|
||||||
|
"""Маскированный токен для placeholder: abc...xyz."""
|
||||||
|
token = current_app.config["NUBES_API_TOKEN"]
|
||||||
|
if not token or len(token) < 8:
|
||||||
|
return ""
|
||||||
|
return token[:4] + "*" * (len(token) - 8) + token[-4:]
|
||||||
+1
-1
@@ -18,7 +18,7 @@ from routes.api import bp as api_bp
|
|||||||
from routes.api_test import bp as api_test_bp
|
from routes.api_test import bp as api_test_bp
|
||||||
|
|
||||||
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
|
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
|
||||||
VERSION = "1.0.89"
|
VERSION = "1.0.90"
|
||||||
|
|
||||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||||
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc")
|
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc")
|
||||||
|
|||||||
+34
-40
@@ -29,6 +29,7 @@ import os
|
|||||||
import fcntl
|
import fcntl
|
||||||
|
|
||||||
from api.http_client import HttpClient, detect_endpoint, stand_name
|
from api.http_client import HttpClient, detect_endpoint, stand_name
|
||||||
|
from api.auth import get_token, get_client, get_client_id, get_stand
|
||||||
from operations.get_services import get_services, get_service_detail
|
from operations.get_services import get_services, get_service_detail
|
||||||
from operations.get_instances import get_instances
|
from operations.get_instances import get_instances
|
||||||
from operations.tracker import add as tracker_add, list_all as tracker_list
|
from operations.tracker import add as tracker_add, list_all as tracker_list
|
||||||
@@ -92,37 +93,10 @@ def _unique_display_name(client, requested_name):
|
|||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
def _client():
|
|
||||||
token = request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"]
|
|
||||||
endpoint = detect_endpoint(token) or current_app.config["NUBES_API_ENDPOINT"]
|
|
||||||
return HttpClient(endpoint, token)
|
|
||||||
|
|
||||||
|
|
||||||
def _client_id():
|
|
||||||
"""Извлечь ClientID из JWT токена (из cookie или env)."""
|
|
||||||
import base64, json
|
|
||||||
token = request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"]
|
|
||||||
try:
|
|
||||||
parts = token.split(".")
|
|
||||||
if len(parts) >= 2:
|
|
||||||
payload = base64.urlsafe_b64decode(parts[1] + "==")
|
|
||||||
return json.loads(payload).get("ClientID", "?")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
|
|
||||||
def _stand():
|
|
||||||
"""dev/test — по токену."""
|
|
||||||
token = request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"]
|
|
||||||
endpoint = detect_endpoint(token) or current_app.config["NUBES_API_ENDPOINT"]
|
|
||||||
return stand_name(endpoint)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/api/services")
|
@bp.route("/api/services")
|
||||||
def api_services():
|
def api_services():
|
||||||
try:
|
try:
|
||||||
raw = get_services(_client())
|
raw = get_services(get_client())
|
||||||
svc_list = [{"svcId": s["svcId"], "svc": s["svc"], "svcExtendedName": s.get("svcExtendedName", "")} for s in raw]
|
svc_list = [{"svcId": s["svcId"], "svc": s["svc"], "svcExtendedName": s.get("svcExtendedName", "")} for s in raw]
|
||||||
svc_list.sort(key=lambda s: s["svcId"])
|
svc_list.sort(key=lambda s: s["svcId"])
|
||||||
return jsonify(svc_list)
|
return jsonify(svc_list)
|
||||||
@@ -133,7 +107,7 @@ def api_services():
|
|||||||
@bp.route("/api/instances/list")
|
@bp.route("/api/instances/list")
|
||||||
def api_instances_list():
|
def api_instances_list():
|
||||||
try:
|
try:
|
||||||
raw = get_instances(_client())
|
raw = get_instances(get_client())
|
||||||
inst = [{"instanceUid": i["instanceUid"], "displayName": i["displayName"], "serviceId": i["serviceId"], "svc": i["svc"], "explainedStatus": i.get("explainedStatus", "?")} for i in raw]
|
inst = [{"instanceUid": i["instanceUid"], "displayName": i["displayName"], "serviceId": i["serviceId"], "svc": i["svc"], "explainedStatus": i.get("explainedStatus", "?")} for i in raw]
|
||||||
return jsonify(inst)
|
return jsonify(inst)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -143,13 +117,13 @@ def api_instances_list():
|
|||||||
@bp.route("/api/operations/<int:svc_id>")
|
@bp.route("/api/operations/<int:svc_id>")
|
||||||
def api_operations(svc_id):
|
def api_operations(svc_id):
|
||||||
try:
|
try:
|
||||||
detail = get_service_detail(_client(), svc_id)
|
detail = get_service_detail(get_client(), svc_id)
|
||||||
ops = detail.get("operations", [])
|
ops = detail.get("operations", [])
|
||||||
# только отслеживаемые инстансы этого сервиса
|
# только отслеживаемые инстансы этого сервиса
|
||||||
tracked = tracker_list(_client_id(), _stand())
|
tracked = tracker_list(get_client_id(), get_stand())
|
||||||
tracked_by_uid = {t["instanceUid"]: t for t in tracked if t["svcId"] == svc_id}
|
tracked_by_uid = {t["instanceUid"]: t for t in tracked if t["svcId"] == svc_id}
|
||||||
tracked_uids = set(tracked_by_uid.keys())
|
tracked_uids = set(tracked_by_uid.keys())
|
||||||
instances = get_instances(_client())
|
instances = get_instances(get_client())
|
||||||
nubes_uids = {i["instanceUid"] for i in instances}
|
nubes_uids = {i["instanceUid"] for i in instances}
|
||||||
svc_instances = [i for i in instances
|
svc_instances = [i for i in instances
|
||||||
if i.get("instanceUid") in tracked_uids
|
if i.get("instanceUid") in tracked_uids
|
||||||
@@ -182,7 +156,7 @@ def api_params(op_id):
|
|||||||
|
|
||||||
if instance_uid:
|
if instance_uid:
|
||||||
# Текущие значения: GET /instances/{uid} → state.params + шаблон → слияние
|
# Текущие значения: GET /instances/{uid} → state.params + шаблон → слияние
|
||||||
result = get_params_with_current_values(_client(), op_id, instance_uid)
|
result = get_params_with_current_values(get_client(), op_id, instance_uid)
|
||||||
_log(f"[api_params] MERGED returning {len(result)} params from state.params")
|
_log(f"[api_params] MERGED returning {len(result)} params from state.params")
|
||||||
for p in result:
|
for p in result:
|
||||||
_log(f"[api_params] {p['name']}: defaultValue={p['defaultValue']!r}")
|
_log(f"[api_params] {p['name']}: defaultValue={p['defaultValue']!r}")
|
||||||
@@ -190,7 +164,7 @@ def api_params(op_id):
|
|||||||
|
|
||||||
# Без instanceUid — шаблонные значения по умолчанию (CREATE)
|
# Без instanceUid — шаблонные значения по умолчанию (CREATE)
|
||||||
_log(f"[api_params] DEFAULT branch: template defaults")
|
_log(f"[api_params] DEFAULT branch: template defaults")
|
||||||
data = _client().get(f"/instanceOperations/default/{op_id}")
|
data = get_client().get(f"/instanceOperations/default/{op_id}")
|
||||||
params = data["svcOperation"]["cfsParams"]
|
params = data["svcOperation"]["cfsParams"]
|
||||||
result = []
|
result = []
|
||||||
for p in params:
|
for p in params:
|
||||||
@@ -226,7 +200,7 @@ def api_test():
|
|||||||
instance_uid = data.get("instanceUid")
|
instance_uid = data.get("instanceUid")
|
||||||
display_name = data.get("displayName") or ""
|
display_name = data.get("displayName") or ""
|
||||||
|
|
||||||
client = _client()
|
client = get_client()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if op_name == "create":
|
if op_name == "create":
|
||||||
@@ -248,7 +222,7 @@ def api_test():
|
|||||||
# Записать в трекер СРАЗУ после получения instanceUid, до params/run
|
# Записать в трекер СРАЗУ после получения instanceUid, до params/run
|
||||||
# (если params упадут — инстанс уже отслежен, сирот не будет)
|
# (если params упадут — инстанс уже отслежен, сирот не будет)
|
||||||
try:
|
try:
|
||||||
tracker_add(_client_id(), _stand(), instance_uid, svc_id, display_name)
|
tracker_add(get_client_id(), get_stand(), instance_uid, svc_id, display_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
print(f"[TRACKER ERROR] add failed: {e}", flush=True)
|
print(f"[TRACKER ERROR] add failed: {e}", flush=True)
|
||||||
@@ -260,7 +234,7 @@ def api_test():
|
|||||||
client.post(f"/instanceOperations/{op_uid}/run")
|
client.post(f"/instanceOperations/{op_uid}/run")
|
||||||
|
|
||||||
# фоном ждать завершения
|
# фоном ждать завершения
|
||||||
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, True, _client_id(), _stand()), daemon=True).start()
|
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, True, get_client_id(), get_stand()), daemon=True).start()
|
||||||
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name})
|
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -290,7 +264,7 @@ def api_test():
|
|||||||
# Получить реальное имя инстанса из API (не UUID!)
|
# Получить реальное имя инстанса из API (не UUID!)
|
||||||
if not display_name:
|
if not display_name:
|
||||||
display_name = _get_instance_display_name(client, instance_uid)
|
display_name = _get_instance_display_name(client, instance_uid)
|
||||||
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, False, _client_id(), _stand(), is_delete), daemon=True).start()
|
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, False, get_client_id(), get_stand(), is_delete), daemon=True).start()
|
||||||
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name})
|
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -298,8 +272,25 @@ def api_test():
|
|||||||
return jsonify({"status": "FAIL", "error": str(e) + " | " + traceback.format_exc()[-200:]})
|
return jsonify({"status": "FAIL", "error": str(e) + " | " + traceback.format_exc()[-200:]})
|
||||||
|
|
||||||
|
|
||||||
# Результаты фоновых операций: opUid → {status, error, stages, duration}
|
# Результаты фоновых операций: opUid → {status, error, stages, duration, _ts}
|
||||||
|
# _ts — timestamp добавления, для TTL-очистки (макс. 500 записей или старше 1 часа)
|
||||||
_op_results = {}
|
_op_results = {}
|
||||||
|
_MAX_OP_RESULTS = 500
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup_op_results():
|
||||||
|
"""Удалить старые записи: старше 1 часа или сверх лимита."""
|
||||||
|
import time
|
||||||
|
now = time.time()
|
||||||
|
# Удалить старше 1 часа
|
||||||
|
stale = [k for k, v in _op_results.items() if now - v.get("_ts", 0) > 3600]
|
||||||
|
for k in stale:
|
||||||
|
del _op_results[k]
|
||||||
|
# Если всё ещё много — удалить самые старые
|
||||||
|
if len(_op_results) > _MAX_OP_RESULTS:
|
||||||
|
sorted_keys = sorted(_op_results.keys(), key=lambda k: _op_results[k].get("_ts", 0))
|
||||||
|
for k in sorted_keys[:len(_op_results) - _MAX_OP_RESULTS]:
|
||||||
|
del _op_results[k]
|
||||||
|
|
||||||
|
|
||||||
def _get_instance_display_name(client, instance_uid):
|
def _get_instance_display_name(client, instance_uid):
|
||||||
@@ -315,6 +306,7 @@ def _get_instance_display_name(client, instance_uid):
|
|||||||
def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, is_create, client_id, stand, is_delete=False):
|
def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, is_create, client_id, stand, is_delete=False):
|
||||||
"""Фоном ждать dtFinish и сохранить результат."""
|
"""Фоном ждать dtFinish и сохранить результат."""
|
||||||
import time
|
import time
|
||||||
|
_cleanup_op_results() # очистить старые записи перед добавлением новой
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
deadline = t0 + 300
|
deadline = t0 + 300
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
@@ -332,6 +324,7 @@ def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_
|
|||||||
"displayName": display_name,
|
"displayName": display_name,
|
||||||
"stages": op.get("stages", []),
|
"stages": op.get("stages", []),
|
||||||
"duration": round(time.time() - t0, 1),
|
"duration": round(time.time() - t0, 1),
|
||||||
|
"_ts": time.time(),
|
||||||
}
|
}
|
||||||
if dt_finish and str(dt_finish).strip():
|
if dt_finish and str(dt_finish).strip():
|
||||||
is_ok = op.get("isSuccessful")
|
is_ok = op.get("isSuccessful")
|
||||||
@@ -346,6 +339,7 @@ def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_
|
|||||||
"error": str(err) if err else "",
|
"error": str(err) if err else "",
|
||||||
"stages": op.get("stages", []),
|
"stages": op.get("stages", []),
|
||||||
"duration": round(time.time() - t0, 1),
|
"duration": round(time.time() - t0, 1),
|
||||||
|
"_ts": time.time(),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@@ -353,7 +347,7 @@ def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_
|
|||||||
import traceback
|
import traceback
|
||||||
print(f"[ERROR] _finish_op crashed: {traceback.format_exc()}", flush=True)
|
print(f"[ERROR] _finish_op crashed: {traceback.format_exc()}", flush=True)
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
_op_results[op_uid] = {"status": "TIMEOUT", "displayName": display_name, "duration": round(time.time() - t0, 1)}
|
_op_results[op_uid] = {"status": "TIMEOUT", "displayName": display_name, "duration": round(time.time() - t0, 1), "_ts": time.time()}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/api/test/status/<op_uid>")
|
@bp.route("/api/test/status/<op_uid>")
|
||||||
|
|||||||
+6
-46
@@ -6,14 +6,12 @@ GET /api/operations/<svc_id> — операции и autotest-инста
|
|||||||
|
|
||||||
Вспомогательные функции:
|
Вспомогательные функции:
|
||||||
_resolve_instance_status() — канонический статус (cloud или "creating" из трекера)
|
_resolve_instance_status() — канонический статус (cloud или "creating" из трекера)
|
||||||
_mask() — маскировка токена (env: abc...xyz)
|
|
||||||
_client_id() — извлечение ClientID из JWT
|
|
||||||
_token_info() — email, компания, clientId из JWT
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import Blueprint, current_app, render_template, request, make_response, jsonify, redirect
|
from flask import Blueprint, current_app, render_template, request, make_response, jsonify, redirect
|
||||||
|
|
||||||
from api.http_client import HttpClient, detect_endpoint, create_client, stand_name
|
from api.http_client import HttpClient, detect_endpoint, create_client, stand_name
|
||||||
|
from api.auth import get_token, get_client_id, get_token_info, get_token_masked
|
||||||
from operations.get_instances import get_organization, get_instances
|
from operations.get_instances import get_organization, get_instances
|
||||||
from operations.get_services import get_services, get_service_detail
|
from operations.get_services import get_services, get_service_detail
|
||||||
from operations.tracker import list_all as tracker_list
|
from operations.tracker import list_all as tracker_list
|
||||||
@@ -40,44 +38,6 @@ def _resolve_instance_status(instance, tracked=None):
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
def _mask(s):
|
|
||||||
"""Маскировка токена для показа в placeholder: abc...xyz."""
|
|
||||||
if not s or len(s) < 8:
|
|
||||||
return ""
|
|
||||||
return s[:4] + "*" * (len(s) - 8) + s[-4:]
|
|
||||||
|
|
||||||
|
|
||||||
def _client_id(token):
|
|
||||||
"""Извлечение ClientID из payload JWT-токена (base64, без проверки подписи)."""
|
|
||||||
import base64, json
|
|
||||||
try:
|
|
||||||
parts = token.split(".") # header.payload.signature
|
|
||||||
if len(parts) >= 2:
|
|
||||||
payload = base64.urlsafe_b64decode(parts[1] + "==") # padding
|
|
||||||
return json.loads(payload).get("ClientID", "")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _token_info(token):
|
|
||||||
"""Извлечение email, company, client_id из JWT для показа в топбаре."""
|
|
||||||
import base64, json
|
|
||||||
try:
|
|
||||||
parts = token.split(".")
|
|
||||||
if len(parts) >= 2:
|
|
||||||
payload = base64.urlsafe_b64decode(parts[1] + "==")
|
|
||||||
d = json.loads(payload)
|
|
||||||
return {
|
|
||||||
"email": d.get("email", ""),
|
|
||||||
"company": d.get("company_name", ""),
|
|
||||||
"client_id": d.get("ClientID", ""),
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/", methods=["GET", "POST"])
|
@bp.route("/", methods=["GET", "POST"])
|
||||||
def index():
|
def index():
|
||||||
"""Главная страница: организация, инфраструктура, сервисы, форма токена.
|
"""Главная страница: организация, инфраструктура, сервисы, форма токена.
|
||||||
@@ -106,10 +66,10 @@ def index():
|
|||||||
ctx = {
|
ctx = {
|
||||||
"organization": org,
|
"organization": org,
|
||||||
"error": error,
|
"error": error,
|
||||||
"env_token_masked": _mask(env_token),
|
"env_token_masked": get_token_masked(),
|
||||||
"has_user_token": bool(user_token),
|
"has_user_token": bool(user_token),
|
||||||
"client_id": _client_id(active_token),
|
"client_id": get_client_id(),
|
||||||
"token_info": _token_info(active_token),
|
"token_info": get_token_info(),
|
||||||
"config": config,
|
"config": config,
|
||||||
"stand": stand,
|
"stand": stand,
|
||||||
"services": services,
|
"services": services,
|
||||||
@@ -130,7 +90,7 @@ def index():
|
|||||||
user_token = request.form.get("token", "")
|
user_token = request.form.get("token", "")
|
||||||
active_token = user_token or env_token
|
active_token = user_token or env_token
|
||||||
resp = make_response()
|
resp = make_response()
|
||||||
resp.set_cookie("token", user_token, max_age=60*60*24*365) # 1 год
|
resp.set_cookie("token", user_token, max_age=60*60*24*365, httponly=True, samesite="Strict") # 1 год
|
||||||
resp.headers["Location"] = "/" # редирект на GET (убирает POST из истории)
|
resp.headers["Location"] = "/" # редирект на GET (убирает POST из истории)
|
||||||
resp.status_code = 302
|
resp.status_code = 302
|
||||||
return resp
|
return resp
|
||||||
@@ -214,7 +174,7 @@ def api_operations(svc_id):
|
|||||||
ops = detail.get("operations", [])
|
ops = detail.get("operations", [])
|
||||||
|
|
||||||
# Трекер: наши autotest-инстансы (изолирован по пользователю и стенду)
|
# Трекер: наши autotest-инстансы (изолирован по пользователю и стенду)
|
||||||
tracked = tracker_list(_client_id(active_token), stand_name(endpoint))
|
tracked = tracker_list(get_client_id(), stand_name(endpoint))
|
||||||
tracked_by_uid = {t["instanceUid"]: t for t in tracked if t["svcId"] == svc_id}
|
tracked_by_uid = {t["instanceUid"]: t for t in tracked if t["svcId"] == svc_id}
|
||||||
|
|
||||||
# Все инстансы из облака
|
# Все инстансы из облака
|
||||||
|
|||||||
Reference in New Issue
Block a user