@formixru/b2b-ui 1.8.0 → 1.10.0
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.
- package/.docker/dev/compose/docker-compose.yaml +12 -0
- package/.docker/local/compose/docker-compose.yaml +8 -0
- package/.gitea/workflows/deploy.yaml +237 -0
- package/.gitea/workflows/teardown.yaml +47 -0
- package/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/components/tabs/index.js +72 -73
- package/src/components/tabs/index.scss +5 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
name: deploy
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: ["**"]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
deploy:
|
|
8
|
+
runs-on: dev.optiweb
|
|
9
|
+
env:
|
|
10
|
+
NODE_CONTAINER_LABEL: "20.12-2.0.0"
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Вычисление переменных
|
|
16
|
+
run: |
|
|
17
|
+
echo "BUILD_START=$(date +%s)" >> "$GITEA_ENV"
|
|
18
|
+
|
|
19
|
+
RAW="${{ gitea.ref_name }}"
|
|
20
|
+
|
|
21
|
+
if [[ "$RAW" =~ ^v[0-9]+(\.[0-9]+)+$ ]]; then
|
|
22
|
+
RAW="${RAW#v}"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
VERSION=$(echo "$RAW" | sed 's/[^a-zA-Z0-9]/-/g')
|
|
26
|
+
{
|
|
27
|
+
echo "VERSION=$VERSION"
|
|
28
|
+
echo "PROJECT=${{ gitea.event.repository.name }}"
|
|
29
|
+
echo "DEST=${DEPLOY_ROOT}/${{ gitea.event.repository.name }}/${VERSION}"
|
|
30
|
+
echo "HOST_DEST=${HOST_DEPLOY_ROOT}/${{ gitea.event.repository.name }}/${VERSION}"
|
|
31
|
+
echo "HOST_WORK_DIR=${HOST_WORK_ROOT}${{ gitea.workspace }}"
|
|
32
|
+
echo "COMPOSE_DIR=${DEPLOY_ROOT}/${{ gitea.event.repository.name }}/${VERSION}/.docker/${DEPLOY_TYPE}/compose"
|
|
33
|
+
} >> "$GITEA_ENV"
|
|
34
|
+
|
|
35
|
+
NODE_ADDR=$(curl -sS -H "X-Consul-Token: ${CONSUL_HTTP_TOKEN}" \
|
|
36
|
+
"${CONSUL_HTTP_ADDR}/v1/agent/self" | jq -r '.DebugConfig.AdvertiseAddrLAN')
|
|
37
|
+
if [ -z "$NODE_ADDR" ] || [ "$NODE_ADDR" = "null" ]; then
|
|
38
|
+
echo "::error::Не удалось получить адрес агента"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
echo "NODE_ADDR=$NODE_ADDR" >> "$GITEA_ENV"
|
|
42
|
+
|
|
43
|
+
- name: Версии ПО и переменные с которыми выполняется сборка
|
|
44
|
+
run: |
|
|
45
|
+
echo 'PROJECT:' $PROJECT
|
|
46
|
+
echo 'VERSION:' $VERSION
|
|
47
|
+
echo 'DEST:' $DEST
|
|
48
|
+
echo 'HOST_DEST:' $HOST_DEST
|
|
49
|
+
echo 'HOST_WORK_DIR:' $HOST_WORK_DIR
|
|
50
|
+
echo 'PLACEHOLDER:' ${PLACEHOLDER}
|
|
51
|
+
echo 'BASE_DOMAIN:' ${BASE_DOMAIN}
|
|
52
|
+
echo 'DEPLOY_TYPE:' ${DEPLOY_TYPE}
|
|
53
|
+
echo 'COMPOSE_DIR:' ${COMPOSE_DIR}
|
|
54
|
+
echo 'NODE_ADDR:' ${NODE_ADDR}
|
|
55
|
+
|
|
56
|
+
- name: Запуск сборки
|
|
57
|
+
run: |
|
|
58
|
+
docker run --rm -i \
|
|
59
|
+
-e YARN_CACHE_FOLDER=/.yarn-cache \
|
|
60
|
+
-v $HOST_WORK_DIR:/app \
|
|
61
|
+
-v yarn-cache:/.yarn-cache \
|
|
62
|
+
--pull always gitea.optiweb.ru/public/node:${NODE_CONTAINER_LABEL} \
|
|
63
|
+
sh -c "yarn install && yarn build && yarn lint"
|
|
64
|
+
|
|
65
|
+
- name: Копирование собранных файлов
|
|
66
|
+
run: |
|
|
67
|
+
mkdir -p "$DEST"
|
|
68
|
+
rsync -a --info=copy,del --delete --exclude '.git' ./ "$DEST/"
|
|
69
|
+
|
|
70
|
+
- name: Запуск контейнеров
|
|
71
|
+
run: docker compose -f ${COMPOSE_DIR}/docker-compose.yaml -p "${PROJECT}_${VERSION}" up -d --pull always --remove-orphans
|
|
72
|
+
|
|
73
|
+
- name: Дерегистрация прежних записей версии
|
|
74
|
+
run: |
|
|
75
|
+
SVC_NAME="${VERSION}-${PROJECT}-${PLACEHOLDER}"
|
|
76
|
+
curl -sS -H "X-Consul-Token: ${CONSUL_HTTP_TOKEN}" \
|
|
77
|
+
"${CONSUL_HTTP_ADDR}/v1/agent/services" | \
|
|
78
|
+
jq -r --arg name "$SVC_NAME" \
|
|
79
|
+
'to_entries[] | select(.value.Service == $name) | .key' | \
|
|
80
|
+
while read -r sid; do
|
|
81
|
+
echo "Дерегистрирую прежнюю запись: $sid"
|
|
82
|
+
curl -sS -H "X-Consul-Token: ${CONSUL_HTTP_TOKEN}" \
|
|
83
|
+
-X PUT "${CONSUL_HTTP_ADDR}/v1/agent/service/deregister/${sid}"
|
|
84
|
+
done
|
|
85
|
+
|
|
86
|
+
- name: Регистрация сервисов в consul
|
|
87
|
+
run: |
|
|
88
|
+
cd "${COMPOSE_DIR}"
|
|
89
|
+
COMPOSE="docker compose -f docker-compose.yaml -p ${PROJECT}_${VERSION}"
|
|
90
|
+
SVC_NAME="${VERSION}-${PROJECT}-${PLACEHOLDER}"
|
|
91
|
+
|
|
92
|
+
ALL_TAGS="" # накопитель всех тегов ingress-сервисов
|
|
93
|
+
|
|
94
|
+
for service in $($COMPOSE ps --services); do
|
|
95
|
+
for cid in $($COMPOSE ps -q "$service"); do
|
|
96
|
+
[ -z "$cid" ] && continue
|
|
97
|
+
|
|
98
|
+
tags=""; additional_meta=""; ingress=""; first=true
|
|
99
|
+
while IFS='=' read -r key value; do
|
|
100
|
+
key=$(echo "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
101
|
+
value=$(echo "$value" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/^"\(.*\)"$/\1/')
|
|
102
|
+
case "$key" in
|
|
103
|
+
consul.meta.ingress)
|
|
104
|
+
ingress="$value" ;;
|
|
105
|
+
consul.meta.*)
|
|
106
|
+
mkey=$(echo "$key" | sed 's/^consul\.meta\.//')
|
|
107
|
+
$first && first=false || additional_meta="${additional_meta},"
|
|
108
|
+
additional_meta="${additional_meta}\"$mkey\":\"$value\"" ;;
|
|
109
|
+
consul.tags)
|
|
110
|
+
tags=$(echo "$value" | sed 's/[^,]*/"&"/g') ;;
|
|
111
|
+
esac
|
|
112
|
+
done < <(docker inspect --format='{{range $k,$v := .Config.Labels}}{{$k}}={{$v}}{{"\n"}}{{end}}' "$cid" | grep '^consul')
|
|
113
|
+
|
|
114
|
+
if [ -z "$tags" ]; then
|
|
115
|
+
echo "$service: без consul.tags, пропуск"; continue
|
|
116
|
+
fi
|
|
117
|
+
if [ "$ingress" != "true" ]; then
|
|
118
|
+
echo "$service: ingress не true (='$ingress'), пропуск роутинга"; continue
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
pnum=$(docker inspect --format='{{range $p,$c := .NetworkSettings.Ports}}{{$p}} {{end}}' "$cid" | awk '{print $1}' | cut -d/ -f1)
|
|
122
|
+
[ -z "$pnum" ] && { echo "$service: нет портов, пропуск"; continue; }
|
|
123
|
+
pext=$($COMPOSE port "$service" "$pnum" | cut -d: -f2)
|
|
124
|
+
[ -z "$pext" ] && { echo "$service: порт не опубликован, пропуск"; continue; }
|
|
125
|
+
|
|
126
|
+
# накапливаем теги этого сервиса (снимаем кавычки, что sed навесил для JSON)
|
|
127
|
+
plain_tags=$(echo "$tags" | sed 's/"//g; s/,/ /g')
|
|
128
|
+
ALL_TAGS="${ALL_TAGS} ${plain_tags}"
|
|
129
|
+
|
|
130
|
+
meta="\"project\":\"${PROJECT}\",\"version\":\"${VERSION}\",\"placeholder\":\"${PLACEHOLDER}\",\"ingress\":\"${ingress}\""
|
|
131
|
+
[ -n "$additional_meta" ] && meta="${meta},${additional_meta}"
|
|
132
|
+
|
|
133
|
+
BODY="{
|
|
134
|
+
\"ID\": \"${cid}\",
|
|
135
|
+
\"Name\": \"${SVC_NAME}\",
|
|
136
|
+
\"Address\": \"${NODE_ADDR}\",
|
|
137
|
+
\"Port\": ${pext},
|
|
138
|
+
\"Tags\": [${tags}],
|
|
139
|
+
\"Meta\": {${meta}},
|
|
140
|
+
\"Check\": {
|
|
141
|
+
\"TCP\": \"127.0.0.1:${pext}\",
|
|
142
|
+
\"Interval\": \"10s\"
|
|
143
|
+
}
|
|
144
|
+
}"
|
|
145
|
+
|
|
146
|
+
echo "Регистрирую $service cid=${cid:0:12} порт=$pext теги=[$tags] ingress=$ingress"
|
|
147
|
+
RESP=$(curl -sS -w "\n%{http_code}" \
|
|
148
|
+
-H "X-Consul-Token: ${CONSUL_HTTP_TOKEN}" \
|
|
149
|
+
-H 'Content-Type: application/json' \
|
|
150
|
+
-X PUT --data "$BODY" \
|
|
151
|
+
"${CONSUL_HTTP_ADDR}/v1/agent/service/register")
|
|
152
|
+
CODE=$(echo "$RESP" | tail -n1)
|
|
153
|
+
if [ "$CODE" != "200" ]; then
|
|
154
|
+
echo "::warning::Регистрация $service вернула $CODE: $(echo "$RESP" | head -n-1)"
|
|
155
|
+
fi
|
|
156
|
+
done
|
|
157
|
+
done
|
|
158
|
+
|
|
159
|
+
# нормализуем список тегов (убираем лишние пробелы) и сохраняем для шага уведомления
|
|
160
|
+
ALL_TAGS=$(echo "$ALL_TAGS" | tr -s ' ' | sed 's/^ //; s/ $//')
|
|
161
|
+
echo "ALL_TAGS=$ALL_TAGS" >> "$GITEA_ENV"
|
|
162
|
+
echo "Собранные теги для уведомления: $ALL_TAGS"
|
|
163
|
+
|
|
164
|
+
- name: Уведомление в Telegram
|
|
165
|
+
if: always()
|
|
166
|
+
run: |
|
|
167
|
+
STATUS="${{ job.status }}"
|
|
168
|
+
case "$STATUS" in
|
|
169
|
+
success) ICON="✅"; WORD="успешно" ;;
|
|
170
|
+
failure) ICON="❌"; WORD="с ошибкой" ;;
|
|
171
|
+
*) ICON="⚠️"; WORD="$STATUS" ;;
|
|
172
|
+
esac
|
|
173
|
+
|
|
174
|
+
# длительность сборки
|
|
175
|
+
if [ -n "$BUILD_START" ]; then
|
|
176
|
+
ELAPSED=$(( $(date +%s) - BUILD_START ))
|
|
177
|
+
MIN=$(( ELAPSED / 60 ))
|
|
178
|
+
SEC=$(( ELAPSED % 60 ))
|
|
179
|
+
if [ "$MIN" -gt 0 ]; then
|
|
180
|
+
DURATION="${MIN} м ${SEC} с"
|
|
181
|
+
else
|
|
182
|
+
DURATION="${SEC} с"
|
|
183
|
+
fi
|
|
184
|
+
else
|
|
185
|
+
DURATION="—"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
md_escape() {
|
|
189
|
+
printf '%s' "$1" | sed 's/[\\_*`\[]/\\&/g'
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
COMMIT_MSG_RAW=$(git log -1 --pretty=%s 2>/dev/null || echo "—")
|
|
193
|
+
COMMIT_MSG=$(md_escape "$COMMIT_MSG_RAW")
|
|
194
|
+
REF=$(md_escape "${{ gitea.ref_name }}")
|
|
195
|
+
ACTOR=$(md_escape "${{ gitea.actor }}")
|
|
196
|
+
PROJECT_ESC=$(md_escape "$PROJECT")
|
|
197
|
+
|
|
198
|
+
SHA_FULL="${{ gitea.sha }}"
|
|
199
|
+
SHA_SHORT="${SHA_FULL:0:8}"
|
|
200
|
+
COMMIT_URL="${{ gitea.server_url }}/${{ gitea.repository }}/commit/${SHA_FULL}"
|
|
201
|
+
|
|
202
|
+
DOMAINS=""
|
|
203
|
+
for tag in $ALL_TAGS; do
|
|
204
|
+
DOMAINS="${DOMAINS}\nhttp://${tag}.${VERSION}.${PROJECT}.${PLACEHOLDER}.${BASE_DOMAIN}"
|
|
205
|
+
done
|
|
206
|
+
[ -z "$ALL_TAGS" ] && DOMAINS="\n(нет ingress-сервисов)"
|
|
207
|
+
|
|
208
|
+
MESSAGE=$(printf '%s Деплой %s / %s завершён %s\nВетка: %s\nКоммит: [%s](%s)\nСообщение: %s\nАвтор: %s\nВремя сборки: %s\nПлощадка: %s.%s\nАдреса:%b' \
|
|
209
|
+
"$ICON" "$PROJECT_ESC" "$VERSION" "$WORD" \
|
|
210
|
+
"$REF" "$SHA_SHORT" "$COMMIT_URL" "$COMMIT_MSG" "$ACTOR" \
|
|
211
|
+
"$DURATION" "$PLACEHOLDER" "$BASE_DOMAIN" "$DOMAINS")
|
|
212
|
+
|
|
213
|
+
HTTP_BODY=$(curl -sS --max-time 15 \
|
|
214
|
+
-U "${TELEGRAM_PROXY_LOGIN}:${TELEGRAM_PROXY_PASS}" \
|
|
215
|
+
-x "${TELEGRAM_PROXY_ADDRESS}" \
|
|
216
|
+
-w "\nHTTP_CODE=%{http_code}" \
|
|
217
|
+
-X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
|
|
218
|
+
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
219
|
+
--data-urlencode "text=${MESSAGE}" \
|
|
220
|
+
--data-urlencode "parse_mode=Markdown" \
|
|
221
|
+
--data-urlencode "disable_web_page_preview=true" 2>&1)
|
|
222
|
+
RC=$?
|
|
223
|
+
echo "curl exit=$RC"
|
|
224
|
+
echo "response: $HTTP_BODY"
|
|
225
|
+
if [ $RC -ne 0 ]; then
|
|
226
|
+
echo "::warning::curl завершился с кодом $RC — $HTTP_BODY"
|
|
227
|
+
elif ! echo "$HTTP_BODY" | grep -q 'HTTP_CODE=200'; then
|
|
228
|
+
echo "::warning::Telegram API вернул не 200 — $HTTP_BODY"
|
|
229
|
+
else
|
|
230
|
+
echo "Уведомление отправлено"
|
|
231
|
+
fi
|
|
232
|
+
env:
|
|
233
|
+
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
|
234
|
+
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
|
235
|
+
TELEGRAM_PROXY_ADDRESS: ${{ secrets.TELEGRAM_PROXY_ADDRESS }}
|
|
236
|
+
TELEGRAM_PROXY_LOGIN: ${{ secrets.TELEGRAM_PROXY_LOGIN }}
|
|
237
|
+
TELEGRAM_PROXY_PASS: ${{ secrets.TELEGRAM_PROXY_PASS }}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: teardown
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
teardown:
|
|
7
|
+
runs-on: optiweb
|
|
8
|
+
steps:
|
|
9
|
+
- name: Вычисление переменных
|
|
10
|
+
run: |
|
|
11
|
+
RAW="${{ gitea.ref_name }}"
|
|
12
|
+
|
|
13
|
+
if [[ "$RAW" =~ ^v[0-9]+(\.[0-9]+)+$ ]]; then
|
|
14
|
+
RAW="${RAW#v}"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
VERSION=$(echo "$RAW" | sed 's/[^a-zA-Z0-9]/-/g')
|
|
18
|
+
{
|
|
19
|
+
echo "VERSION=$VERSION"
|
|
20
|
+
echo "PROJECT=${{ gitea.event.repository.name }}"
|
|
21
|
+
echo "DEST=${DEPLOY_ROOT}/${{ gitea.event.repository.name }}/${VERSION}"
|
|
22
|
+
echo "HOST_DEST=${HOST_DEPLOY_ROOT}/${{ gitea.event.repository.name }}/${VERSION}"
|
|
23
|
+
echo "COMPOSE_DIR=${DEPLOY_ROOT}/${{ gitea.event.repository.name }}/${VERSION}/.docker/${DEPLOY_TYPE}/compose"
|
|
24
|
+
} >> "$GITEA_ENV"
|
|
25
|
+
|
|
26
|
+
- name: Дерегистрация записей версии в consul
|
|
27
|
+
run: |
|
|
28
|
+
SVC_NAME="${VERSION}-${PROJECT}-${PLACEHOLDER}"
|
|
29
|
+
FOUND=0
|
|
30
|
+
curl -sS -H "X-Consul-Token: ${CONSUL_HTTP_TOKEN}" \
|
|
31
|
+
"${CONSUL_HTTP_ADDR}/v1/agent/services" | jq -r --arg name "$SVC_NAME" \
|
|
32
|
+
'to_entries[] | select(.value.Service == $name) | .key' | \
|
|
33
|
+
while read -r sid; do
|
|
34
|
+
echo "Дерегистрирую: $sid"
|
|
35
|
+
curl -sS -H "X-Consul-Token: ${CONSUL_HTTP_TOKEN}" \
|
|
36
|
+
-X PUT "${CONSUL_HTTP_ADDR}/v1/agent/service/deregister/${sid}"
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
- name: Остановка контейнеров
|
|
40
|
+
run: |
|
|
41
|
+
if [ -d "$COMPOSE_DIR" ]; then
|
|
42
|
+
docker compose -f "$COMPOSE_DIR/docker-compose.yaml" -p "${PROJECT}_${VERSION}" down --remove-orphans
|
|
43
|
+
else
|
|
44
|
+
echo "compose-каталог не найден, останавливаю по метке проекта"
|
|
45
|
+
docker ps -q --filter "label=com.docker.compose.project=${PROJECT}_${VERSION}" \
|
|
46
|
+
| xargs -r docker rm -f
|
|
47
|
+
fi
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -19,14 +19,25 @@ const setLeftWidth = async ($line, $el) => {
|
|
|
19
19
|
}).finished;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const setActive = ($el, active) => {
|
|
23
|
+
if ($el.tagName === 'BUTTON') {
|
|
24
|
+
$el.disabled = active;
|
|
25
|
+
} else if (active) {
|
|
26
|
+
$el.dataset.disabled = '';
|
|
27
|
+
} else {
|
|
28
|
+
delete $el.dataset.disabled;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
22
32
|
export const setInitLineStyles = ($b) => {
|
|
23
33
|
const $nav = $b.querySelector('.tabs__nav');
|
|
24
34
|
const $item = $nav.querySelectorAll('.tabs__item');
|
|
25
35
|
const $line = $nav.querySelector('.tabs__line');
|
|
26
36
|
|
|
27
|
-
const $el =
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
const $el =
|
|
38
|
+
[...$item].find((el) => {
|
|
39
|
+
return el.tagName === 'BUTTON' ? el.disabled : el.dataset.disabled !== undefined;
|
|
40
|
+
}) || $item[0];
|
|
30
41
|
|
|
31
42
|
setLeftWidth($line, $el);
|
|
32
43
|
// Прокручиваем до нужного таба
|
|
@@ -54,83 +65,71 @@ export const init = async ($b) => {
|
|
|
54
65
|
const handleItemClick = async (e) => {
|
|
55
66
|
const $el = e.currentTarget;
|
|
56
67
|
const index = [...$item].indexOf($el);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
$el.disabled = true;
|
|
78
|
-
for (const [i, c] of $content.entries()) {
|
|
79
|
-
if (i === index) {
|
|
80
|
-
c.hidden = false;
|
|
81
|
-
for (const swiperEl of c.querySelectorAll('.swiper')) {
|
|
82
|
-
if (swiperEl.swiper) {
|
|
83
|
-
swiperEl.swiper.update();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
for (const tabsEl of c.querySelectorAll('.tabs')) {
|
|
87
|
-
setInitLineStyles(tabsEl);
|
|
88
|
-
}
|
|
89
|
-
// Инициализируем полученный HTML
|
|
90
|
-
initTabsContent($content[index]);
|
|
91
|
-
} else {
|
|
92
|
-
c.hidden = true;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
} catch (error) {
|
|
96
|
-
hideLoader();
|
|
97
|
-
if (error.status === 401) {
|
|
98
|
-
location.reload();
|
|
99
|
-
} else {
|
|
100
|
-
errorHandler(error);
|
|
101
|
-
}
|
|
68
|
+
|
|
69
|
+
// Ссылка с нативным поведением — отдаём клик браузеру
|
|
70
|
+
if ($el.dataset.tabsNative !== undefined) return;
|
|
71
|
+
|
|
72
|
+
if ($el.tagName === 'A') {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if ($el.dataset.tabsFetch && $content[index].innerHTML.trim() === '') {
|
|
77
|
+
showLoader();
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch($el.dataset.tabsFetch, {
|
|
80
|
+
method: 'GET',
|
|
81
|
+
headers: {
|
|
82
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
83
|
+
'X-Session-Id': BX.message('bitrix_sessid'),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw response;
|
|
102
88
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
$
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
for (const tabsEl of c.querySelectorAll('.tabs')) {
|
|
116
|
-
setInitLineStyles(tabsEl);
|
|
117
|
-
}
|
|
118
|
-
// Инициализируем полученный HTML
|
|
119
|
-
initTabsContent($content[index]);
|
|
120
|
-
} else {
|
|
121
|
-
c.hidden = true;
|
|
122
|
-
}
|
|
89
|
+
const data = await response.text();
|
|
90
|
+
hideLoader();
|
|
91
|
+
// Вставляем полученный HTML
|
|
92
|
+
$content[index].innerHTML = data;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
hideLoader();
|
|
95
|
+
if (error.status === 401) {
|
|
96
|
+
location.reload();
|
|
97
|
+
} else {
|
|
98
|
+
errorHandler(error);
|
|
123
99
|
}
|
|
100
|
+
return;
|
|
124
101
|
}
|
|
102
|
+
}
|
|
125
103
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
104
|
+
// Переключаем таб
|
|
105
|
+
for (const i of $item) setActive(i, false);
|
|
106
|
+
setActive($el, true);
|
|
129
107
|
|
|
130
|
-
|
|
131
|
-
|
|
108
|
+
for (const [i, c] of $content.entries()) {
|
|
109
|
+
if (i === index) {
|
|
110
|
+
c.hidden = false;
|
|
111
|
+
for (const swiperEl of c.querySelectorAll('.swiper')) {
|
|
112
|
+
if (swiperEl.swiper) {
|
|
113
|
+
swiperEl.swiper.update();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const tabsEl of c.querySelectorAll('.tabs')) {
|
|
117
|
+
setInitLineStyles(tabsEl);
|
|
118
|
+
}
|
|
119
|
+
// Инициализируем полученный HTML
|
|
120
|
+
initTabsContent($content[index]);
|
|
121
|
+
} else {
|
|
122
|
+
c.hidden = true;
|
|
132
123
|
}
|
|
133
124
|
}
|
|
125
|
+
|
|
126
|
+
if ($nav.swiper) {
|
|
127
|
+
$nav.swiper.slideTo(index);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if ($line) {
|
|
131
|
+
setLeftWidth($line, $el);
|
|
132
|
+
}
|
|
134
133
|
};
|
|
135
134
|
|
|
136
135
|
if ($nav.swiper) {
|