@coopenomics/desktop 2025.11.29-2 → 2025.12.2-2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coopenomics/desktop",
|
|
3
|
-
"version": "2025.
|
|
3
|
+
"version": "2025.12.2-2",
|
|
4
4
|
"description": "A Desktop Project",
|
|
5
5
|
"productName": "Desktop App",
|
|
6
6
|
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"start": "node -r ./alias-resolver.js dist/ssr/index.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@coopenomics/controller": "2025.
|
|
29
|
-
"@coopenomics/notifications": "2025.
|
|
30
|
-
"@coopenomics/sdk": "2025.
|
|
28
|
+
"@coopenomics/controller": "2025.12.2-2",
|
|
29
|
+
"@coopenomics/notifications": "2025.12.2-2",
|
|
30
|
+
"@coopenomics/sdk": "2025.12.2-2",
|
|
31
31
|
"@dicebear/collection": "^9.0.1",
|
|
32
32
|
"@dicebear/core": "^9.0.1",
|
|
33
33
|
"@editorjs/code": "^2.9.3",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@wharfkit/wallet-plugin-privatekey": "^1.1.0",
|
|
60
60
|
"axios": "^1.2.1",
|
|
61
61
|
"compression": "^1.7.4",
|
|
62
|
-
"cooptypes": "2025.
|
|
62
|
+
"cooptypes": "2025.12.2-2",
|
|
63
63
|
"dompurify": "^3.1.7",
|
|
64
64
|
"dotenv": "^16.4.5",
|
|
65
65
|
"email-regex": "^5.0.0",
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"npm": ">= 6.13.4",
|
|
124
124
|
"yarn": ">= 1.21.1"
|
|
125
125
|
},
|
|
126
|
-
"gitHead": "
|
|
126
|
+
"gitHead": "e552840cc42ff1e352880c51cc24587ed2c97c6c"
|
|
127
127
|
}
|
|
@@ -15,15 +15,11 @@ export async function useInitAppProcess(router: Router) {
|
|
|
15
15
|
applyThemeFromStorage();
|
|
16
16
|
const system = useSystemStore();
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} catch (error) {
|
|
24
|
-
console.warn('Failed to load initial system info, backend might be unavailable:', error);
|
|
25
|
-
// Продолжаем инициализацию даже при недоступности бэкенда
|
|
26
|
-
}
|
|
18
|
+
try {
|
|
19
|
+
await system.loadSystemInfo();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.warn('Failed to load initial system info, backend might be unavailable:', error);
|
|
22
|
+
// Продолжаем инициализацию даже при недоступности бэкенда
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
// Запускаем мониторинг системной информации для отслеживания статуса
|
|
@@ -19,15 +19,21 @@ div.settings-form
|
|
|
19
19
|
template(v-slot:prepend v-if="item.property.description?.prepend")
|
|
20
20
|
span {{ item.property.description.prepend }}
|
|
21
21
|
|
|
22
|
-
// Слот для append, если указано
|
|
23
|
-
template(v-slot:append
|
|
24
|
-
span {{ item.property.description.append }}
|
|
22
|
+
// Слот для append, если указано или нужна иконка видимости пароля/копирования
|
|
23
|
+
template(v-slot:append)
|
|
24
|
+
span(v-if="item.property.description?.append") {{ item.property.description.append }}
|
|
25
|
+
q-icon.cursor-pointer(
|
|
26
|
+
v-if="shouldShowCopyIcon(item.property) || item.property.description?.password"
|
|
27
|
+
:name="getIconName(item.propertyName)"
|
|
28
|
+
@click="handleIconClick(item.propertyName)"
|
|
29
|
+
)
|
|
25
30
|
|
|
26
31
|
</template>
|
|
27
32
|
<script lang="ts" setup>
|
|
28
33
|
import { defineProps, defineEmits, reactive, watch, computed } from 'vue';
|
|
29
|
-
import { QInput, QCheckbox, QSelect } from 'quasar';
|
|
34
|
+
import { QInput, QCheckbox, QSelect, copyToClipboard } from 'quasar';
|
|
30
35
|
import type { IExtensionConfigSchema, ISchemaProperty } from 'src/entities/Extension/model';
|
|
36
|
+
import { SuccessAlert } from 'src/shared/api/alerts';
|
|
31
37
|
|
|
32
38
|
// Устанавливаем имя компонента для рекурсивного вызова
|
|
33
39
|
defineOptions({
|
|
@@ -38,10 +44,86 @@ div.settings-form
|
|
|
38
44
|
const props = defineProps<{
|
|
39
45
|
schema: IExtensionConfigSchema;
|
|
40
46
|
modelValue: Record<string, any>;
|
|
47
|
+
/** Режим установки - включает генерацию значений для полей с generator */
|
|
48
|
+
installMode?: boolean;
|
|
41
49
|
}>();
|
|
42
50
|
|
|
43
51
|
const emit = defineEmits(['update:modelValue']);
|
|
44
52
|
|
|
53
|
+
// Состояние видимости паролей для каждого поля
|
|
54
|
+
const passwordVisibility = reactive<Record<string, boolean>>({});
|
|
55
|
+
|
|
56
|
+
// Функция переключения видимости пароля
|
|
57
|
+
function togglePasswordVisibility(propertyName: string) {
|
|
58
|
+
passwordVisibility[propertyName] = !passwordVisibility[propertyName];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
// Получение имени иконки для поля
|
|
63
|
+
function getIconName(propertyName: string): string | undefined {
|
|
64
|
+
const property = visibleProperties.value.find(p => p.propertyName === propertyName)?.property;
|
|
65
|
+
|
|
66
|
+
if (shouldShowCopyIcon(property)) {
|
|
67
|
+
return 'content_copy'; // Иконка копирования
|
|
68
|
+
} else if (property?.description?.password) {
|
|
69
|
+
return passwordVisibility[propertyName] ? 'visibility' : 'visibility_off';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Определяем, нужно ли показывать иконку копирования
|
|
76
|
+
function shouldShowCopyIcon(property: ISchemaProperty | undefined): boolean {
|
|
77
|
+
if (!property?.description) return false;
|
|
78
|
+
|
|
79
|
+
// В режиме установки показываем копирование для полей с generator или copyable
|
|
80
|
+
if (props.installMode) {
|
|
81
|
+
return !!(property.description.generator || property.description.copyable);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// В обычном режиме показываем копирование только для полей с copyable
|
|
85
|
+
return !!property.description.copyable;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Обработка клика по иконке
|
|
89
|
+
function handleIconClick(propertyName: string) {
|
|
90
|
+
const property = visibleProperties.value.find(p => p.propertyName === propertyName)?.property;
|
|
91
|
+
|
|
92
|
+
if (shouldShowCopyIcon(property)) {
|
|
93
|
+
// Копируем значение поля в буфер обмена
|
|
94
|
+
const valueToCopy = data[propertyName] || props.modelValue?.[propertyName] || '';
|
|
95
|
+
copyToClipboard(valueToCopy);
|
|
96
|
+
|
|
97
|
+
// Показываем уведомление об успешном копировании
|
|
98
|
+
SuccessAlert('Значение скопировано в буфер обмена');
|
|
99
|
+
} else if (property?.description?.password) {
|
|
100
|
+
// Переключаем видимость пароля
|
|
101
|
+
togglePasswordVisibility(propertyName);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Генерирует криптографически стойкую случайную строку
|
|
107
|
+
* @returns Hex-строка длиной 64 символа (256 бит)
|
|
108
|
+
*/
|
|
109
|
+
function generateRandomSecret(): string {
|
|
110
|
+
const array = new Uint8Array(32);
|
|
111
|
+
crypto.getRandomValues(array);
|
|
112
|
+
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Генерирует значение для поля на основе типа генератора
|
|
117
|
+
*/
|
|
118
|
+
function generateValue(generatorType: string): any {
|
|
119
|
+
switch (generatorType) {
|
|
120
|
+
case 'randomSecret':
|
|
121
|
+
return generateRandomSecret();
|
|
122
|
+
default:
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
45
127
|
// Вычисляемое свойство для видимых свойств
|
|
46
128
|
const visibleProperties = computed(() => {
|
|
47
129
|
return Object.entries(props.schema.properties)
|
|
@@ -55,16 +137,27 @@ div.settings-form
|
|
|
55
137
|
// Инициализируем реактивные данные
|
|
56
138
|
const data = reactive<Record<string, any>>({});
|
|
57
139
|
|
|
58
|
-
// Функция для установки значений по умолчанию
|
|
59
|
-
function setDefaults(schema: ISchemaProperty, obj: any) {
|
|
140
|
+
// Функция для установки значений по умолчанию (с генерацией при необходимости)
|
|
141
|
+
function setDefaults(schema: ISchemaProperty, obj: any, sourceValues?: Record<string, any>) {
|
|
60
142
|
if (schema.type === 'object' && schema.properties) {
|
|
61
143
|
for (const key in schema.properties) {
|
|
62
144
|
const property = schema.properties[key];
|
|
63
|
-
|
|
145
|
+
const existingValue = sourceValues?.[key];
|
|
146
|
+
|
|
147
|
+
// Если есть существующее значение (непустая строка для строковых полей)
|
|
148
|
+
if (existingValue !== undefined && existingValue !== null && existingValue !== '') {
|
|
149
|
+
obj[key] = existingValue;
|
|
150
|
+
} else if (props.installMode && property.description?.generator) {
|
|
151
|
+
// В режиме установки генерируем значение для полей с generator
|
|
152
|
+
obj[key] = generateValue(property.description.generator);
|
|
153
|
+
} else if (props.installMode && property.description?.default !== undefined) {
|
|
154
|
+
// В режиме установки устанавливаем значение из поля default
|
|
155
|
+
obj[key] = property.description.default;
|
|
156
|
+
} else if (property.default !== undefined) {
|
|
64
157
|
obj[key] = property.default;
|
|
65
158
|
} else if (property.type === 'object') {
|
|
66
159
|
obj[key] = {};
|
|
67
|
-
setDefaults(property, obj[key]);
|
|
160
|
+
setDefaults(property, obj[key], existingValue);
|
|
68
161
|
} else {
|
|
69
162
|
obj[key] = null;
|
|
70
163
|
}
|
|
@@ -74,11 +167,15 @@ div.settings-form
|
|
|
74
167
|
|
|
75
168
|
// Инициализация данных
|
|
76
169
|
if (props.modelValue && Object.keys(props.modelValue).length > 0) {
|
|
77
|
-
|
|
170
|
+
// Есть существующие данные - используем их, но проверяем на генерацию
|
|
171
|
+
setDefaults(props.schema, data, props.modelValue);
|
|
78
172
|
} else {
|
|
79
173
|
setDefaults(props.schema, data);
|
|
80
174
|
}
|
|
81
175
|
|
|
176
|
+
// Эмитим начальные данные (важно для сгенерированных значений)
|
|
177
|
+
emit('update:modelValue', { ...data });
|
|
178
|
+
|
|
82
179
|
// Отслеживание изменений и обновление данных
|
|
83
180
|
watch(
|
|
84
181
|
data,
|
|
@@ -113,7 +210,7 @@ div.settings-form
|
|
|
113
210
|
}
|
|
114
211
|
|
|
115
212
|
function getComponentProps(property: ISchemaProperty, propertyName: string) {
|
|
116
|
-
const
|
|
213
|
+
const componentProps: Record<string, any> = {
|
|
117
214
|
modelValue: data[propertyName],
|
|
118
215
|
'onUpdate:modelValue': (value: any) => {
|
|
119
216
|
data[propertyName] = property.type === 'number' ? parseFloat(value) : value;
|
|
@@ -128,46 +225,61 @@ div.settings-form
|
|
|
128
225
|
|
|
129
226
|
if (typeof minLength === 'number') {
|
|
130
227
|
rules.push((val: string) => val.length >= minLength || `Минимальная длина: ${minLength}`);
|
|
131
|
-
|
|
228
|
+
componentProps.minLength = minLength;
|
|
132
229
|
}
|
|
133
230
|
|
|
134
231
|
if (typeof maxLength === 'number') {
|
|
135
232
|
rules.push((val: string) => val.length <= maxLength || `Максимальная длина: ${maxLength}`);
|
|
136
|
-
|
|
233
|
+
componentProps.maxLength = maxLength;
|
|
137
234
|
}
|
|
138
235
|
|
|
139
|
-
|
|
236
|
+
componentProps.rules = rules
|
|
140
237
|
|
|
141
238
|
// Добавляем маску и другие настройки, если они указаны
|
|
142
239
|
if (property.description?.mask) {
|
|
143
|
-
|
|
240
|
+
componentProps.mask = property.description.mask;
|
|
144
241
|
if (property.description?.fillMask !== undefined) {
|
|
145
|
-
|
|
242
|
+
componentProps.fillMask = property.description.fillMask;
|
|
146
243
|
}
|
|
147
244
|
}
|
|
148
245
|
|
|
246
|
+
// Поддержка readonly
|
|
247
|
+
if (property.description?.readonly) {
|
|
248
|
+
componentProps.readonly = true;
|
|
249
|
+
}
|
|
250
|
+
|
|
149
251
|
// Установка типа поля в зависимости от типа property
|
|
150
252
|
if (property.type === 'number') {
|
|
151
|
-
|
|
253
|
+
componentProps.type = 'number'; // Поле будет восприниматься как числовое, разрешены только цифры
|
|
152
254
|
} else if (property.type === 'string') {
|
|
153
|
-
|
|
255
|
+
// Проверка на пароль
|
|
256
|
+
if (property.description?.password) {
|
|
257
|
+
// В режиме установки показываем пароль как обычный текст для копирования
|
|
258
|
+
if (props.installMode) {
|
|
259
|
+
componentProps.type = 'text';
|
|
260
|
+
} else {
|
|
261
|
+
componentProps.type = passwordVisibility[propertyName] ? 'text' : 'password';
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
componentProps.type = 'text'; // Поле для строк
|
|
265
|
+
}
|
|
154
266
|
// Проверка на многосстрочный ввод
|
|
155
267
|
if (property.description?.maxRows) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
268
|
+
componentProps.type = 'textarea';
|
|
269
|
+
componentProps.autogrow = true; // Автоматический рост поля при вводе
|
|
270
|
+
componentProps.rows = property.description?.maxRows; // Установка максимального количества строк
|
|
159
271
|
}
|
|
160
272
|
}
|
|
161
273
|
|
|
162
274
|
if (property.enum) {
|
|
163
|
-
|
|
275
|
+
componentProps.options = property.enum;
|
|
164
276
|
}
|
|
165
277
|
|
|
166
278
|
if (property.type === 'object') {
|
|
167
|
-
|
|
279
|
+
componentProps.schema = property;
|
|
168
280
|
}
|
|
169
281
|
|
|
170
|
-
return
|
|
282
|
+
return componentProps;
|
|
171
283
|
}
|
|
172
284
|
|
|
173
285
|
function getLabel(property: ISchemaProperty, propertyName: string | number) {
|
|
@@ -2,15 +2,9 @@
|
|
|
2
2
|
.info-card
|
|
3
3
|
q-form(ref='formRef')
|
|
4
4
|
div(v-if='schema && !isEmpty')
|
|
5
|
-
ClientOnly
|
|
6
|
-
template(#default)
|
|
7
|
-
vue-markdown.description.q-mt-md(
|
|
8
|
-
v-if='instructions',
|
|
9
|
-
:source='instructions'
|
|
10
|
-
)
|
|
11
5
|
div
|
|
12
6
|
p.text-h5 Настройки
|
|
13
|
-
ZodForm.q-mt-lg(:schema='schema', :model-value='config', @update:model-value='$emit("update:config", $event)')
|
|
7
|
+
ZodForm.q-mt-lg(:schema='schema', :model-value='config', :install-mode='true', @update:model-value='$emit("update:config", $event)')
|
|
14
8
|
div(v-else)
|
|
15
9
|
.q-pa-md
|
|
16
10
|
p.text-h5 Установка расширения
|
|
@@ -18,20 +12,13 @@
|
|
|
18
12
|
</template>
|
|
19
13
|
|
|
20
14
|
<script lang="ts" setup>
|
|
21
|
-
import { computed
|
|
15
|
+
import { computed } from 'vue';
|
|
22
16
|
import { ZodForm } from 'src/shared/ui/ZodForm';
|
|
23
|
-
import { ClientOnly } from 'src/shared/ui/ClientOnly';
|
|
24
17
|
import { isExtensionSchemaEmpty } from 'src/shared/lib/utils';
|
|
25
18
|
|
|
26
|
-
// Клиентский компонент для markdown, загружаемый только на клиенте
|
|
27
|
-
const VueMarkdown = defineAsyncComponent(() =>
|
|
28
|
-
import('vue-markdown-render').then((mod) => mod.default),
|
|
29
|
-
);
|
|
30
|
-
|
|
31
19
|
interface Props {
|
|
32
20
|
schema?: any;
|
|
33
21
|
config: any;
|
|
34
|
-
instructions?: string;
|
|
35
22
|
formRef?: any;
|
|
36
23
|
}
|
|
37
24
|
|