chore: initial import for test contour with k3s CI

This commit is contained in:
sova-bootstrap
2026-05-28 12:09:28 +03:00
commit d77d0a872f
423 changed files with 35401 additions and 0 deletions
+357
View File
@@ -0,0 +1,357 @@
const Cookies = require('js-cookie');
function serializeFormData(obj, prefix = '') {
const str = [];
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
const newKey = prefix ? `${prefix}[${key}]` : key;
if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof File) && !(value instanceof Blob)) {
// Рекурсивно обрабатываем вложенные объекты
str.push(serializeFormData(value, newKey));
} else if (Array.isArray(value)) {
// Обрабатываем массивы
value.forEach((item, index) => {
if (typeof item === 'object' && item !== null && !(item instanceof File) && !(item instanceof Blob)) {
str.push(serializeFormData(item, `${newKey}[${index}]`));
} else {
str.push(encodeURIComponent(`${newKey}[${index}]`) + '=' + encodeURIComponent(item));
}
});
} else {
str.push(encodeURIComponent(newKey) + '=' + encodeURIComponent(value));
}
}
}
return str.join('&');
}
function sendRequest(data, url, method = "POST", dataType = "json", withCredentials = true, contentType = null) {
return new Promise(async (resolve, reject) => {
try {
const methodUpper = method.toUpperCase();
const isGetOrHead = methodUpper === "GET" || methodUpper === "HEAD";
// Для GET/HEAD перемещаем данные в URL
if (isGetOrHead && data) {
const params = new URLSearchParams(data);
url = `${url}${url.includes('?') ? '&' : '?'}${params}`;
}
// Автоматическое определение content-type для не-GET запросов
let finalContentType = contentType;
if (!finalContentType && !isGetOrHead) {
finalContentType = "application/x-www-form-urlencoded";
if (data && Object.values(data).some(value => value instanceof File || value instanceof Blob)) {
finalContentType = "multipart/form-data";
}
}
let body = null;
let headers = {};
// Подготовка тела только для не-GET запросов
if (!isGetOrHead && data) {
switch (finalContentType) {
case "application/json":
body = JSON.stringify(data);
headers["Content-Type"] = "application/json";
break;
case "multipart/form-data":
body = new FormData();
Object.entries(data).forEach(([key, value]) => {
body.append(key, value);
});
break;
default:
// Используем правильную сериализацию для вложенных объектов
body = serializeFormData(data);
headers["Content-Type"] = "application/x-www-form-urlencoded";
}
}
const response = await fetch(url, {
method: methodUpper,
headers: headers,
credentials: withCredentials ? "include" : "same-origin",
body: body
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const responseData = await response[dataType]();
resolve(responseData);
} catch (error) {
reject(error);
}
});
}
function isMobile() {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|Opera Mini/i.test(navigator.userAgent)) {
return true;
}
return false;
}
function getCityId(address) {
const cityMap = new Map([
['Волгоград', 96],
['Воронеж', 98],
['Краснодар', 3018],
['Саратов', 94]
]);
for (const [cityName, cityId] of cityMap) {
if (address.includes(cityName)) {
return cityId;
}
}
return null;
}
function getCityCRM(region) {
switch (region) {
case '92': return 96; // Волгоград
case '93': return 98; // Воронеж
case '94': return 3018; // Краснодар
default: return 94; // Саратов
}
}
function getLicensePersonLink() {
if (/cabinet\.sovamed\.ru/m.test(location.hostname) || /cabinet\.wmtmed\.ru/m.test(location.hostname)) {
return 'https://cabinet.sovamed.ru/docs/soglasie-cabinet.pdf';
}
return 'https://cabinet.sovamed.ru/docs/soglasie-site.pdf';
}
function getLicenseLink(region) {
switch (region) {
case '92': // Волгоград
return 'https://volgograd.sovamed.ru/o-sove/dokumenty-i-licenzii/politika-konfidencialnosti/';
case '93': // Воронеж
return 'https://voronezh.sovamed.ru/o-sove/dokumenty-i-licenzii/politika-konfidencialnosti/';
case '94': // Краснодар
return 'https://wmtmed.ru/about/confidentiality_policy.php';
case '95': // Совенок
return 'https://sovenok.sovamed.ru/o-sove/dokumenty-i-licenzii/politika-konfidencialnosti/';
case '96': // Комфорт
return 'https://sovamed.ru/docs/comfort_politika.pdf';
default: // Саратов
return 'https://sovamed.ru/o-sove/dokumenty-i-licenzii/politika-konfidencialnosti/';
}
}
function renderCapcha(wrap) {
const elem = document.getElementById('smart-captcha');
if (elem) {
elem.parentNode.removeChild(elem);
}
wrap.innerHTML = '';
var recaptcha = document.createElement('div');
recaptcha.id = "smart-captcha";
recaptcha.dataset.controller = "smartCaptcha";
wrap.append(recaptcha);
var validRecaptcha = document.createElement('div');
validRecaptcha.classList = "msg-valid valid-captcha";
wrap.append(validRecaptcha);
return recaptcha;
}
function countDown(options) {
var timer,
instance = this,
seconds = options.seconds || 10,
updateStatus = options.onUpdateStatus || function () {},
counterEnd = options.onCounterEnd || function () {};
function decrementCounter() {
updateStatus(seconds);
if (seconds === 0) {
counterEnd();
instance.stop();
}
seconds--;
}
this.start = function () {
clearInterval(timer);
timer = 0;
seconds = options.seconds;
timer = setInterval(decrementCounter, 1000);
};
this.stop = function () {
clearInterval(timer);
};
}
function getApiHostname() {
if (/sovamed\.ru/m.test(location.hostname)) {
return 'https://api.sovamed.ru';
}
return 'https://api.wmtmed.ru';
}
function getHostname() {
if (/sovamed\.ru/m.test(location.hostname)) {
return 'https://cabinet.sovamed.ru';
}
return 'https://cabinet.wmtmed.ru';
}
function getRegionIdByHost() {
switch (location.host) {
case 'volgograd.sovamed.ru':
// Волгоград
var regionId = 92;
break;
case 'voronezh.sovamed.ru':
// Воронеж
var regionId = 93;
break;
case 'sovenok.sovamed.ru':
// Совенок
var regionId = 95;
break;
case 'comfort.sovamed.ru':
// Совенок
var regionId = 96;
break;
case 'wmtmed.ru':
// Краснодар
var regionId = 94;
break;
default:
// Саратов
var regionId = 91;
break;
};
return regionId;
}
const SESSION_ID_PARAM = '_ct_session_id';
function readSessionIdCookie() {
if (typeof document === 'undefined') {
return null;
}
const v = Cookies.get(SESSION_ID_PARAM);
if (v == null || String(v).trim() === '') {
return null;
}
return String(v).trim();
}
/**
* Если в query есть sessionId — пишет session-cookie (до закрытия браузера) и убирает параметр из URL.
*/
function syncSessionIdFromUrl() {
if (typeof window === 'undefined' || !window.location) {
return;
}
const url = new URL(window.location.href);
const raw = url.searchParams.get(SESSION_ID_PARAM);
if (raw === null) {
return;
}
const value = String(raw).trim();
if (!value) {
url.searchParams.delete(SESSION_ID_PARAM);
const next = url.pathname + url.search + url.hash;
if (next !== window.location.pathname + window.location.search + window.location.hash) {
window.location.replace(next);
}
return;
}
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
document.cookie = `${SESSION_ID_PARAM}=${encodeURIComponent(value)}; path=/; SameSite=Lax${secure}`;
url.searchParams.delete(SESSION_ID_PARAM);
window.location.replace(url.pathname + url.search + url.hash);
}
function addAlert(msg, alertSystem, id, color = 'alert-danger') {
var alert = document.createElement('div');
alert.id = id;
alert.classList = 'alert alert-dismissible fade show';
alert.classList.add(color);
alert.setAttribute('role', 'alert');
var divMsg = document.createElement('div');
divMsg.classList = 'alert-msg';
divMsg.innerHTML = msg;
alert.append(divMsg);
alertSystem.prepend(alert);
}
function getSessionId() {
const fromCt = window.ct?.('calltracking_params')?.[0]?.sessionId;
if (fromCt != null && String(fromCt).trim() !== '') {
return String(fromCt).trim();
}
const fromCookie = readSessionIdCookie();
if (fromCookie) {
return fromCookie;
}
return null;
}
function addUrlParam(url, param, value) {
const urlObj = new URL(url);
if (!urlObj.searchParams.has(param)) {
urlObj.searchParams.set(param, value);
}
return urlObj.toString();
}
module.exports = {
addUrlParam: addUrlParam,
getSessionId: getSessionId,
syncSessionIdFromUrl: syncSessionIdFromUrl,
addAlert: addAlert,
getRegionIdByHost: getRegionIdByHost,
getApiHostname : getApiHostname,
getHostname: getHostname,
countDown: countDown,
isMobile: isMobile,
sendRequest: sendRequest,
getLicensePersonLink: getLicensePersonLink,
getLicenseLink: getLicenseLink,
getCityCRM: getCityCRM,
getCityId: getCityId,
renderCapcha: renderCapcha
};
+89
View File
@@ -0,0 +1,89 @@
const helper = require("./helper.js");
function esia() {
if (document.location.search == '?esia=true') {
const hostName = helper.getHostname();
window.webSDK.on('init', function() {
if (!window.webSDK?.data?.user?.authenticated)
return;
const user = window.webSDK?.data?.user;
$.ajax({
method: "POST",
crossDomain: false,
url: "/api/authenticated",
contentType: "application/x-www-form-urlencoded",
dataType: "json",
data: {
user: user,
uid: user.id
},
xhrFields: {
withCredentials: true
},
success(response) {
if (response.data.success == true) {
const parser = document.createElement('a');
parser.href = response.data.redirect;
window.location.replace(document.location.origin + parser.pathname + parser.search);
}
}
});
});
}
};
function loadSDK(controller) {
return new Promise(function(resolve, reject) {
var script = document.getElementById('sdk-infoclinica');
if (script == null) {
var script = document.createElement('script');
if (/sovamed\.ru/m.test(location.hostname)) {
script.src = 'https://widget.sovamed.ru/assets/javascripts/sdk/sdk.build.min.js';
} else {
script.src = 'https://widget.wmtmed.ru/assets/javascripts/sdk/sdk.build.min.js';
}
script.id = "sdk-infoclinica";
document.head.appendChild(script);
script.addEventListener('load', function() {
console.log(controller + ' - load');
window.webSDK = new WrSDK();
esia();
resolve(window.webSDK);
});
} else {
script.addEventListener('load', function() {
console.log(controller + ' - onload');
esia();
resolve(window.webSDK);
});
}
});
}
function btnLoader(btn, disabled = true) {
if (disabled) {
btn.dataset.name = btn.innerHTML;
btn.style.paddingRight = '5px';
btn.disabled = disabled;
btn.innerHTML = 'Загрузка&nbsp;<img width="20" src="'+ helper.getHostname() +'/images/eclipse.gif">';
} else {
btn.style.paddingRight = null;
btn.disabled = disabled;
btn.innerHTML = btn.dataset.name;
}
}
// Exporting loadSDK function
module.exports = {
loadSDK: loadSDK,
btnLoader: btnLoader
};
+118
View File
@@ -0,0 +1,118 @@
/**
* MIS (webSDK / widget.sovamed.ru) session helpers.
* Symfony login (ROLE_USER) and MIS session are independent.
*/
function MisSessionError() {
this.name = 'MisSessionError';
this.message = 'MIS session is not authenticated';
}
function isAuthenticated(webSDK) {
return Boolean(webSDK?.data?.user?.authenticated);
}
function ensureAuthenticated(webSDK) {
return new Promise(function(resolve, reject) {
if (isAuthenticated(webSDK)) {
resolve(webSDK);
return;
}
if (typeof webSDK?.isLoggedIn === 'function') {
webSDK.isLoggedIn(webSDK.sdkOrigin).then(function(result) {
if (result?.authenticated) {
resolve(webSDK);
return;
}
reject(new MisSessionError());
}).catch(function() {
reject(new MisSessionError());
});
return;
}
reject(new MisSessionError());
});
}
function removeSdkOverlayModals() {
document.querySelectorAll('.wr-sdk-widget-modal').forEach(function(modal) {
modal.remove();
});
}
/**
* Move #iframeProtocol from SDK overlay into cabinet bootstrap popup.
* openConference() must be called without container (SDK applies Guest URL fix).
*/
function mountConferenceInPopup(popup) {
var iframe = document.getElementById('iframeProtocol');
var popupBody = popup?.querySelector('#popup-body');
if (!iframe || !popupBody) {
return false;
}
removeSdkOverlayModals();
popupBody.innerHTML = '';
popupBody.appendChild(iframe);
iframe.style.width = '100%';
iframe.style.border = 'none';
iframe.style.height = (window.innerHeight - 100) + 'px';
var fullScreenBtn = popup.querySelector('.full-scren-modal');
if (fullScreenBtn) {
fullScreenBtn.classList.remove('d-none');
}
popup.querySelector('.modal-dialog').classList = 'modal-dialog';
popup.querySelector('.modal-content').classList = 'modal-content';
popup.querySelector('.modal-title').innerHTML = 'Онлайн консультация';
if (typeof $ !== 'undefined' && typeof $(popup).modal === 'function') {
$(popup).modal('show');
}
return true;
}
function showMisSessionExpired(popup) {
if (!popup) {
window.location.pathname = '/logout';
return;
}
popup.querySelector('.modal-dialog').classList = 'modal-dialog';
popup.querySelector('.modal-title').innerHTML = 'Сессия виджета истекла';
popup.querySelector('#popup-body').innerHTML =
'<p class="mb-3">Личный кабинет открыт, но сессия виджета записи (MIS) истекла. ' +
'Для оплаты и онлайн-приёма нужно войти снова — повторная авторизация в iframe не требуется.</p>' +
'<button type="button" class="btn btn-outline-secondary w-100" id="mis-session-relogin">Войти снова</button>';
popup.querySelector('#mis-session-relogin').addEventListener('click', function() {
window.location.pathname = '/logout';
});
if (typeof $ !== 'undefined' && typeof $(popup).modal === 'function') {
$(popup).modal('show');
}
}
function handleMisSessionFailure(popup, error, logContext) {
if (error instanceof MisSessionError || error?.name === 'MisSessionError') {
showMisSessionExpired(popup);
return true;
}
return false;
}
module.exports = {
MisSessionError: MisSessionError,
isAuthenticated: isAuthenticated,
ensureAuthenticated: ensureAuthenticated,
mountConferenceInPopup: mountConferenceInPopup,
showMisSessionExpired: showMisSessionExpired,
handleMisSessionFailure: handleMisSessionFailure,
};
+34
View File
@@ -0,0 +1,34 @@
/**
* Единая нормализация onlineMode для Stimulus/record.js.
* dataset и API могут отдавать true/false, "1"/"0", 1/0.
*/
function isOnlineMode(value) {
if (value === true || value === 1) {
return true;
}
if (value === false || value === 0 || value === null || value === undefined) {
return false;
}
const normalized = String(value).trim().toLowerCase();
if (normalized === '' || normalized === '0' || normalized === 'false' || normalized === 'no' || normalized === 'off') {
return false;
}
if (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on') {
return true;
}
return false;
}
function toOnlineType(value) {
return isOnlineMode(value) ? 1 : 0;
}
module.exports = {
isOnlineMode,
toOnlineType,
};
File diff suppressed because it is too large Load Diff
+209
View File
@@ -0,0 +1,209 @@
function checkEmail(input, msg) {
if (window.validateEmail(input.value)) {
msg.innerHTML = '';
input.classList.remove('is-invalid');
input.classList.add('is-valid');
return false;
} else {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.remove('is-valid');
input.classList.add('is-invalid');
return true;
}
}
function checkPhone(input, msg) {
if (window.validatePhone(input.value)) {
msg.innerHTML = '';
input.classList.remove('is-invalid');
input.classList.add('is-valid');
return false;
} else {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.remove('is-valid');
input.classList.add('is-invalid');
return true;
}
}
function checkNotEmpty(input, msg) {
var valid = false;
if (input.value === '') {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
} else {
msg.innerHTML = '';
input.classList.add('is-valid');
input.classList.remove('is-invalid');
}
return valid;
}
function checkTextRu(input, msg) {
var valid = false;
if (input.value === '') {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
} else {
msg.innerHTML = '';
input.classList.add('is-valid');
input.classList.remove('is-invalid');
}
if (/[A-Za-z0-9]/.test(input.value)) {
msg.innerHTML = '<span class="text-danger">поле должно заполняться кириллицей</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
}
return valid;
}
function checkAccept(ckeckBox, msg) {
var valid = false;
if (ckeckBox.checked) {
msg.innerHTML = '';
ckeckBox.classList.add('is-valid');
ckeckBox.classList.remove('is-invalid');
} else {
msg.innerHTML = '<span class="text-danger">укажите согласие, на обработку персональных данных</span>';
ckeckBox.classList.add('is-invalid');
ckeckBox.classList.remove('is-valid');
valid = true;
}
return valid;
}
function checkDate(input, msg, range = false) {
var valid = false;
if (range) {
if (/^(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[0-2])\.\d{4}.\-.(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[0-2])\.\d{4}$/.test(input.value)) {
msg.innerHTML = '';
input.classList.add('is-valid');
input.classList.remove('is-invalid');
} else {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
}
} else {
if (/^(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[0-2])\.\d{4}$/.test(input.value)) {
msg.innerHTML = '';
input.classList.add('is-valid');
input.classList.remove('is-invalid');
} else {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
}
}
return valid;
}
function checkInt(input, msg) {
var valid = false;
if (/^\d+$/.test(input.value)) {
msg.innerHTML = '';
input.classList.add('is-valid');
input.classList.remove('is-invalid');
} else {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
}
return valid;
}
function checkInn(input, msg) {
var valid = false;
if (input.value === '') {
msg.innerHTML = '<span class="text-danger">поле является обязательным для заполнения</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
} else {
if (is_valid_inn(input.value)) {
msg.innerHTML = '';
input.classList.add('is-valid');
input.classList.remove('is-invalid');
} else {
msg.innerHTML = '<span class="text-danger">проверьте правильность введённых данных</span>';
input.classList.add('is-invalid');
input.classList.remove('is-valid');
valid = true;
}
}
return valid;
}
function is_valid_inn(i) {
if ( i.match(/\D/) ) return false;
var inn = i.match(/(\d)/g);
if ( inn.length == 10 )
{
return inn[9] == String(((
2*inn[0] + 4*inn[1] + 10*inn[2] +
3*inn[3] + 5*inn[4] + 9*inn[5] +
4*inn[6] + 6*inn[7] + 8*inn[8]
) % 11) % 10);
}
else if ( inn.length == 12 )
{
return inn[10] == String(((
7*inn[0] + 2*inn[1] + 4*inn[2] +
10*inn[3] + 3*inn[4] + 5*inn[5] +
9*inn[6] + 4*inn[7] + 6*inn[8] +
8*inn[9]
) % 11) % 10) && inn[11] == String(((
3*inn[0] + 7*inn[1] + 2*inn[2] +
4*inn[3] + 10*inn[4] + 3*inn[5] +
5*inn[6] + 9*inn[7] + 4*inn[8] +
6*inn[9] + 8*inn[10]
) % 11) % 10);
}
return false;
}
function checkSmartCaptcha(token, msg) {
if (token === '') {
msg.innerHTML = '<span class="text-danger">Подтвердите, что Вы не робот</span>';
return true;
}
msg.innerHTML = '';
return false;
}
module.exports = {
checkInt: checkInt,
checkNotEmpty: checkNotEmpty,
checkDate: checkDate,
checkInn: checkInn,
checkAccept: checkAccept,
checkSmartCaptcha: checkSmartCaptcha,
checkTextRu: checkTextRu,
checkEmail: checkEmail,
checkPhone: checkPhone
};