@andrey4emk/npm-app-front-b24 0.2.0 → 0.4.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/ConfigLogs.vue +94 -23
- package/README.md +111 -8
- package/Subscriptions.vue +1146 -300
- package/package.json +26 -8
- package/types/ConfigLogs.vue.d.ts +33 -0
- package/types/Subscriptions.vue.d.ts +167 -0
package/ConfigLogs.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="config-logs">
|
|
3
|
-
<h2>Уровни логирования</h2>
|
|
3
|
+
<h2 class="config-logs__title">Уровни логирования</h2>
|
|
4
4
|
|
|
5
5
|
<div v-if="loading" class="config-logs__loading">Загрузка...</div>
|
|
6
6
|
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
</table>
|
|
48
48
|
|
|
49
49
|
<div class="config-logs__actions">
|
|
50
|
-
<button @click="saveConfig" :disabled="saving">
|
|
50
|
+
<button @click="saveConfig" :disabled="loading || saving">
|
|
51
51
|
{{ saving ? "Сохранение..." : "Сохранить" }}
|
|
52
52
|
</button>
|
|
53
|
-
<button @click="loadConfig" :disabled="loading">Обновить</button>
|
|
53
|
+
<button @click="loadConfig" :disabled="loading || saving">Обновить</button>
|
|
54
54
|
</div>
|
|
55
55
|
|
|
56
56
|
<div v-if="message" :class="['config-logs__message', messageType === 'ok' ? 'config-logs__message--success' : 'config-logs__message--error']">
|
|
@@ -110,37 +110,97 @@ function getHeaders() {
|
|
|
110
110
|
|
|
111
111
|
function getUrl() {
|
|
112
112
|
const base = props.apiBase.replace(/\/+$/, "");
|
|
113
|
-
|
|
113
|
+
// Имя конфига подставляется в путь URL: без экранирования «#» и «?» обрежут путь,
|
|
114
|
+
// а «/» уведёт запрос на другой маршрут
|
|
115
|
+
return `${base}/api/config/${encodeURIComponent(props.configName)}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Разбирает тело ответа: JSON — объект, любой другой Content-Type — null.
|
|
119
|
+
// Отдельная функция нужна, потому что сервер на неизвестный маршрут отвечает
|
|
120
|
+
// HTML-страницей с кодом 200, и слепой res.json() падает с невнятной ошибкой
|
|
121
|
+
async function parseJsonBody(res) {
|
|
122
|
+
const contentType = res.headers.get("content-type") || "";
|
|
123
|
+
if (!contentType.includes("application/json")) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
return await res.json();
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Один и тот же отказ показывается и при загрузке, и при сохранении.
|
|
134
|
+
// Держим текст в одном месте, чтобы формулировки не разъехались
|
|
135
|
+
const NOT_JSON_ERROR_TEXT = "Неожиданный ответ сервера: тело не JSON. Проверьте apiBase";
|
|
136
|
+
|
|
137
|
+
// Текст ошибки по HTTP-статусу. Если сервер прислал message — он попадает в текст
|
|
138
|
+
// при любом коде, включая 401/403 и 5xx: иначе причина отказа теряется
|
|
139
|
+
function httpErrorText(res, json) {
|
|
140
|
+
if (res.status === 401 || res.status === 403) {
|
|
141
|
+
return json?.message
|
|
142
|
+
? `Нет доступа (${res.status}): ${json.message}`
|
|
143
|
+
: "Нет доступа, проверьте secretKey";
|
|
144
|
+
}
|
|
145
|
+
if (res.status >= 500) {
|
|
146
|
+
return json?.message
|
|
147
|
+
? `Ошибка на сервере (${res.status}): ${json.message}`
|
|
148
|
+
: `Ошибка на сервере (${res.status})`;
|
|
149
|
+
}
|
|
150
|
+
const detail = json?.message ? `: ${json.message}` : "";
|
|
151
|
+
return `Ошибка запроса (${res.status})${detail}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Раскладывает пришедший конфиг по реактивным уровням
|
|
155
|
+
function applyConfig(data) {
|
|
156
|
+
for (const level of levels) {
|
|
157
|
+
if (!data[level.key]) continue;
|
|
158
|
+
config[level.key].enabled = data[level.key].enabled ?? false;
|
|
159
|
+
config[level.key].json = data[level.key].json ?? true;
|
|
160
|
+
config[level.key].color = data[level.key].color ?? "";
|
|
161
|
+
}
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
async function loadConfig() {
|
|
165
|
+
// Пока идёт загрузка или сохранение, повторный запуск запрещён:
|
|
166
|
+
// иначе ответы двух запросов затирают друг друга
|
|
167
|
+
if (loading.value || saving.value) return;
|
|
168
|
+
|
|
117
169
|
loading.value = true;
|
|
118
170
|
error.value = "";
|
|
119
171
|
message.value = "";
|
|
120
172
|
|
|
121
173
|
try {
|
|
122
174
|
const res = await fetch(getUrl(), { headers: getHeaders() });
|
|
123
|
-
const json = await res
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
175
|
+
const json = await parseJsonBody(res);
|
|
176
|
+
|
|
177
|
+
if (!res.ok) {
|
|
178
|
+
error.value = httpErrorText(res, json);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!json) {
|
|
182
|
+
error.value = NOT_JSON_ERROR_TEXT;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (json.status !== "ok" || !json.data) {
|
|
134
186
|
error.value = json.message || "Не удалось загрузить конфиг";
|
|
187
|
+
return;
|
|
135
188
|
}
|
|
189
|
+
|
|
190
|
+
applyConfig(json.data);
|
|
136
191
|
} catch (e) {
|
|
137
|
-
|
|
192
|
+
// Сюда попадает только сбой сети: fetch не бросает на 4xx и 5xx
|
|
193
|
+
error.value = `Сервер недоступен: ${e.message}`;
|
|
138
194
|
} finally {
|
|
139
195
|
loading.value = false;
|
|
140
196
|
}
|
|
141
197
|
}
|
|
142
198
|
|
|
143
199
|
async function saveConfig() {
|
|
200
|
+
// Пока идёт загрузка или сохранение, повторный запуск запрещён:
|
|
201
|
+
// иначе ответы двух запросов затирают друг друга
|
|
202
|
+
if (loading.value || saving.value) return;
|
|
203
|
+
|
|
144
204
|
saving.value = true;
|
|
145
205
|
message.value = "";
|
|
146
206
|
|
|
@@ -159,17 +219,28 @@ async function saveConfig() {
|
|
|
159
219
|
headers: getHeaders(),
|
|
160
220
|
body: JSON.stringify(payload),
|
|
161
221
|
});
|
|
162
|
-
const json = await res
|
|
222
|
+
const json = await parseJsonBody(res);
|
|
163
223
|
|
|
164
|
-
if (
|
|
165
|
-
message.value = json
|
|
166
|
-
messageType.value = "
|
|
167
|
-
|
|
224
|
+
if (!res.ok) {
|
|
225
|
+
message.value = httpErrorText(res, json);
|
|
226
|
+
messageType.value = "error";
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (!json) {
|
|
230
|
+
message.value = NOT_JSON_ERROR_TEXT;
|
|
231
|
+
messageType.value = "error";
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (json.status !== "ok") {
|
|
168
235
|
message.value = json.message || "Ошибка сохранения";
|
|
169
236
|
messageType.value = "error";
|
|
237
|
+
return;
|
|
170
238
|
}
|
|
239
|
+
|
|
240
|
+
message.value = json.message || "Конфиг сохранён";
|
|
241
|
+
messageType.value = "ok";
|
|
171
242
|
} catch (e) {
|
|
172
|
-
message.value =
|
|
243
|
+
message.value = `Сервер недоступен: ${e.message}`;
|
|
173
244
|
messageType.value = "error";
|
|
174
245
|
} finally {
|
|
175
246
|
saving.value = false;
|
|
@@ -190,7 +261,7 @@ onMounted(() => {
|
|
|
190
261
|
background: #fafafa;
|
|
191
262
|
}
|
|
192
263
|
|
|
193
|
-
.config-
|
|
264
|
+
.config-logs__title {
|
|
194
265
|
margin: 0 0 16px 0;
|
|
195
266
|
font-size: 18px;
|
|
196
267
|
}
|
package/README.md
CHANGED
|
@@ -12,7 +12,9 @@ npm install @andrey4emk/npm-app-front-b24
|
|
|
12
12
|
|
|
13
13
|
### Subscriptions
|
|
14
14
|
|
|
15
|
-
Управление подписками на события и
|
|
15
|
+
Управление подписками на события и оффлайн-очередью событий Bitrix24: список подписок,
|
|
16
|
+
подписка и отписка, просмотр и очистка оффлайн-сущностей, повторная запись сущности
|
|
17
|
+
с последующим снятием из очереди.
|
|
16
18
|
|
|
17
19
|
```vue
|
|
18
20
|
<script setup>
|
|
@@ -24,11 +26,28 @@ import Subscriptions from "@andrey4emk/npm-app-front-b24/Subscriptions.vue";
|
|
|
24
26
|
</template>
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
**Props:**
|
|
30
|
+
|
|
31
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
32
|
+
|------|-----|-------------|----------|
|
|
33
|
+
| `b24` | Object | не задан | Клиент Bitrix24 REST API (`B24Frame` из `@bitrix24/b24jssdk`) |
|
|
34
|
+
| `authConnector` | String | `""` | Значение `auth_connector` для методов работы с событиями |
|
|
35
|
+
| `portalDomain` | String | `""` | Адрес портала для ссылок на сущности, `portal.bitrix24.ru` или с `https://` |
|
|
36
|
+
| `timeZone` | String | `""` | Часовой пояс для колонки «Дата», строка IANA (`Europe/Moscow`). Пусто — зона браузера |
|
|
37
|
+
|
|
38
|
+
Клиент ищется в таком порядке: prop `b24`, затем `inject("$b24")` (если приложение сделало
|
|
39
|
+
`app.provide("$b24", client)`), затем глобальный `window.$b24`. Без клиента компонент показывает
|
|
40
|
+
сообщение «Клиент Bitrix24 не инициализирован» и не падает.
|
|
41
|
+
|
|
42
|
+
Адрес портала без `portalDomain` берётся из `client.getTargetOrigin()`, а если его нет, из
|
|
43
|
+
`client.auth.getAuthData().domain`.
|
|
44
|
+
|
|
45
|
+
Автообновление списка сущностей (раз в 3 секунды, включается тумблером) останавливается на
|
|
46
|
+
скрытой вкладке браузера и при уходе со страницы.
|
|
28
47
|
|
|
29
48
|
### ConfigLogs
|
|
30
49
|
|
|
31
|
-
Управление уровнями логирования через `config/log.json
|
|
50
|
+
Управление уровнями логирования через `config/log.json` на сервере.
|
|
32
51
|
|
|
33
52
|
```vue
|
|
34
53
|
<script setup>
|
|
@@ -48,17 +67,81 @@ import ConfigLogs from "@andrey4emk/npm-app-front-b24/ConfigLogs.vue";
|
|
|
48
67
|
| `secretKey` | String | `""` | Ключ авторизации (заголовок `secret`) |
|
|
49
68
|
| `configName` | String | `"log"` | Имя конфига (без `.json`) |
|
|
50
69
|
|
|
70
|
+
Компонент ходит на `GET` и `POST` `${apiBase}/api/config/${configName}` и ждёт JSON вида
|
|
71
|
+
`{ status: "ok", data: {...} }`. Ошибки сети, ответы 401/403 и 5xx и не-JSON тела показываются
|
|
72
|
+
пользователю с кодом и текстом сервера.
|
|
73
|
+
|
|
74
|
+
## Стили
|
|
75
|
+
|
|
76
|
+
Стили компонентов глобальные, без `scoped`, все правила под классами с префиксами
|
|
77
|
+
`subscriptions__*` и `config-logs__*`. Их можно переопределять из CSS приложения по имени класса.
|
|
78
|
+
|
|
79
|
+
Цвета вынесены в кастомные свойства на корневых элементах `.subscriptions` и `.config-logs`,
|
|
80
|
+
например `--subscriptions-table-line` и `--subscriptions-table-head-ink`. Переопределяйте их
|
|
81
|
+
в своём CSS, если нужна перекраска без переписывания правил.
|
|
82
|
+
|
|
83
|
+
В `Subscriptions` на элементах вторым классом стоят старые имена `tooltip`, `tooltipParent`,
|
|
84
|
+
`switch`, `slider`, `main` и хуки без правил `subscriptions__title`, `subscriptions__subtitle`.
|
|
85
|
+
Правил под старые имена в пакете нет, они остаются на элементах навсегда как точки подключения
|
|
86
|
+
для стилей приложения. Таблицы получили класс `subscriptions__table` с нейтральным оформлением:
|
|
87
|
+
правила написаны через `:where()`, поэтому любой селектор приложения по `table.main` их перебьёт.
|
|
88
|
+
|
|
89
|
+
## Типы
|
|
90
|
+
|
|
91
|
+
Декларации props генерируются из исходников (`types/*.vue.d.ts`) и подключены через условие
|
|
92
|
+
`types` в карте `exports` для каждого файла. Ничего подключать вручную не нужно: TypeScript
|
|
93
|
+
потребителя (Vite + vue-tsc) подхватит их сам и будет проверять типы props при импорте
|
|
94
|
+
`@andrey4emk/npm-app-front-b24/Subscriptions.vue` и `.../ConfigLogs.vue`.
|
|
95
|
+
|
|
96
|
+
Требования на стороне потребителя:
|
|
97
|
+
|
|
98
|
+
- `moduleResolution` в `tsconfig.json` должен быть `bundler`, `node16` или `nodenext`.
|
|
99
|
+
При устаревшем `node` карта `exports` не читается, типы молча заменяются общим shim `*.vue`,
|
|
100
|
+
props становятся `any`.
|
|
101
|
+
- Не добавляйте пакет в `compilerOptions.types` и не пишите `/// <reference types=...>`:
|
|
102
|
+
корневой файл деклараций отсутствует, обе конструкции дают ошибку TS2688.
|
|
103
|
+
|
|
51
104
|
## Требования
|
|
52
105
|
|
|
53
|
-
- Vue 3 (`^3.
|
|
106
|
+
- Vue 3 (`^3.5.2`) — peer dependency. Ниже 3.5.2 сгенерированные декларации не компилируются
|
|
107
|
+
при `skipLibCheck: false`
|
|
54
108
|
- Проект-потребитель должен поддерживать компиляцию `.vue` файлов (Vite / webpack + vue-loader)
|
|
55
|
-
- `luxon` (`^3.7.2`) — используется в Subscriptions для форматирования дат
|
|
56
109
|
|
|
57
|
-
|
|
110
|
+
Других зависимостей нет: даты форматируются через `Intl.DateTimeFormat`.
|
|
111
|
+
|
|
112
|
+
## История версий
|
|
113
|
+
|
|
114
|
+
### 0.4.0
|
|
115
|
+
|
|
116
|
+
- `Subscriptions`: prop `timeZone`; автообновление останавливается на скрытой вкладке и
|
|
117
|
+
перезапускается после операций; ошибка загрузки гаснет после удачного обновления
|
|
118
|
+
- `Subscriptions`: массовое удаление считает снятые пачки по ответу портала на каждую команду,
|
|
119
|
+
«Удалить все пустые» считает записи по идентификаторам, тексты отчётов уточнены
|
|
120
|
+
- Стили: сняты глобальные правила под старыми именами `tooltip` и `tooltipParent` (имена на
|
|
121
|
+
элементах остались), добавлены `subscriptions__table` и кастомные свойства цветов
|
|
122
|
+
- `ConfigLogs`: имя конфига экранируется в URL
|
|
123
|
+
- Типы: ручной `index.d.ts` заменён на декларации, генерируемые `vue-tsc` при публикации;
|
|
124
|
+
подключение на стороне потребителя больше не требуется
|
|
125
|
+
- `peerDependencies.vue` сужен до `^3.5.2`
|
|
126
|
+
|
|
127
|
+
### 0.3.0
|
|
128
|
+
|
|
129
|
+
- `Subscriptions`: состояние вкладок внутри компонента, props `b24`, `authConnector`,
|
|
130
|
+
`portalDomain`, адрес портала из клиента вместо захардкоженного домена, ссылки на
|
|
131
|
+
смарт-процессы
|
|
132
|
+
- `Subscriptions`: починены «Изменить» для смарт-процессов и «Удалить все», автообновление
|
|
133
|
+
останавливается при уходе со страницы, ошибки показываются на экране, подтверждение
|
|
134
|
+
удаления вторым кликом вместо `confirm()`
|
|
135
|
+
- `ConfigLogs`: разбор ответа сервера с проверкой `res.ok` и Content-Type
|
|
136
|
+
- Классы стилей с префиксами, добавлены стили тумблера
|
|
137
|
+
- Снята зависимость `luxon`
|
|
138
|
+
- Добавлен `index.d.ts` с типами props (в 0.4.0 заменён на генерируемые декларации)
|
|
58
139
|
|
|
59
|
-
|
|
140
|
+
### 0.2.0
|
|
60
141
|
|
|
61
|
-
|
|
142
|
+
Пакет поставляет исходные `.vue` файлы вместо скомпилированного бандла. Компиляция
|
|
143
|
+
происходит на стороне проекта-потребителя. Импорт по голому имени пакета и файл `style.css`
|
|
144
|
+
из 0.1.x больше не существуют. При обновлении с `0.1.x` измените импорты:
|
|
62
145
|
|
|
63
146
|
```js
|
|
64
147
|
// Было (0.1.x)
|
|
@@ -68,3 +151,23 @@ import "@andrey4emk/npm-app-front-b24/style.css";
|
|
|
68
151
|
// Стало (0.2.0+)
|
|
69
152
|
import Subscriptions from "@andrey4emk/npm-app-front-b24/Subscriptions.vue";
|
|
70
153
|
```
|
|
154
|
+
|
|
155
|
+
## Публикация
|
|
156
|
+
|
|
157
|
+
Версия по semver: `patch` для исправлений, `minor` для новых функций, `major` для ломающих
|
|
158
|
+
изменений. Декларации типов перегенерируются автоматически скриптом `prepublishOnly`, поэтому
|
|
159
|
+
перед публикацией нужны установленные `devDependencies`.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm install
|
|
163
|
+
npm run build:types # обновить types/, закоммитить вместе с правками
|
|
164
|
+
git add .
|
|
165
|
+
git commit -m "upd(client): описание"
|
|
166
|
+
npm version minor # или patch / major
|
|
167
|
+
git push origin main --follow-tags
|
|
168
|
+
npm publish --access public
|
|
169
|
+
npm info @andrey4emk/npm-app-front-b24 version
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Если `npm publish` отвечает 403: проверить `npm whoami`, при необходимости `npm login`,
|
|
173
|
+
для scoped-пакета флаг `--access public` обязателен.
|