cynx-lib 1.3.46 → 1.3.48
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 +1 -1
- package/ui/components/LoginPage.jsx +405 -0
- package/ui/index.cjs +3 -0
- package/ui/index.js +2 -0
- package/utils/utils/storage_manager.cjs +2 -2
- package/utils/utils/storage_manager.js +2 -2
package/package.json
CHANGED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { Fingerprint, Lock } from 'lucide-react';
|
|
3
|
+
import Button from './Button.jsx';
|
|
4
|
+
import Input from './Input.jsx';
|
|
5
|
+
import Spinner from './Spinner.jsx';
|
|
6
|
+
import Divider from './Divider.jsx';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Universal White-Label LoginPage component for cynx-lib.
|
|
10
|
+
* Supports Password login, Multi-Provider Captcha (Cap, Turnstile, reCAPTCHA, custom),
|
|
11
|
+
* Passkeys (WebAuthn / Biometrics), and SSO providers.
|
|
12
|
+
* STRICT NO EMOJIS!
|
|
13
|
+
*/
|
|
14
|
+
export default function LoginPage({
|
|
15
|
+
logo,
|
|
16
|
+
logoAlt = 'Logo',
|
|
17
|
+
title = 'Вход в систему',
|
|
18
|
+
subtitle,
|
|
19
|
+
background = 'var(--grey-1, var(--bg, #f4f5f8))',
|
|
20
|
+
cardMaxWidth = 330,
|
|
21
|
+
|
|
22
|
+
// Password Login
|
|
23
|
+
showPasswordLogin = true,
|
|
24
|
+
usernameLabel = 'Логин',
|
|
25
|
+
passwordLabel = 'Пароль',
|
|
26
|
+
usernamePlaceholder = 'Введите логин',
|
|
27
|
+
passwordPlaceholder = 'Введите пароль',
|
|
28
|
+
submitButtonText = 'Войти',
|
|
29
|
+
submitButtonLoadingText = 'Вход...',
|
|
30
|
+
onSubmit,
|
|
31
|
+
|
|
32
|
+
// Passkey Login
|
|
33
|
+
showPasskey = false,
|
|
34
|
+
passkeyButtonText = 'Войти через Passkey',
|
|
35
|
+
passkeyIcon,
|
|
36
|
+
onPasskeyLogin,
|
|
37
|
+
passkeyLoading = false,
|
|
38
|
+
|
|
39
|
+
// SSO Providers
|
|
40
|
+
ssoProviders = [],
|
|
41
|
+
ssoDividerText,
|
|
42
|
+
|
|
43
|
+
// Captcha Configuration
|
|
44
|
+
captcha,
|
|
45
|
+
|
|
46
|
+
// Errors, Notices & States
|
|
47
|
+
error: externalError,
|
|
48
|
+
notice,
|
|
49
|
+
loading: externalLoading = false,
|
|
50
|
+
|
|
51
|
+
// Custom Slots
|
|
52
|
+
headerSlot,
|
|
53
|
+
footerSlot,
|
|
54
|
+
extraFields,
|
|
55
|
+
noAuthMethodsText = 'Нет доступных способов входа. Обратитесь к администратору.',
|
|
56
|
+
}) {
|
|
57
|
+
const [username, setUsername] = useState('');
|
|
58
|
+
const [password, setPassword] = useState('');
|
|
59
|
+
const [internalError, setInternalError] = useState('');
|
|
60
|
+
const [internalLoading, setInternalLoading] = useState(false);
|
|
61
|
+
const [pageReady, setPageReady] = useState(false);
|
|
62
|
+
const [capToken, setCapToken] = useState('');
|
|
63
|
+
const [passkeyInternalLoading, setPasskeyInternalLoading] = useState(false);
|
|
64
|
+
const capRef = useRef(null);
|
|
65
|
+
|
|
66
|
+
const error = externalError !== undefined ? externalError : internalError;
|
|
67
|
+
const loading = externalLoading || internalLoading;
|
|
68
|
+
const isPasskeyBusy = passkeyLoading || passkeyInternalLoading;
|
|
69
|
+
|
|
70
|
+
const isCaptchaEnabled = !!(captcha && (captcha.enabled !== false && (captcha.provider || captcha.endpoint || captcha.siteKey)));
|
|
71
|
+
const captchaProvider = captcha?.provider || 'cap';
|
|
72
|
+
|
|
73
|
+
// 1. Setup Captcha Event Listeners (Cap Anti-Bot Widget)
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!isCaptchaEnabled || captchaProvider !== 'cap') return;
|
|
76
|
+
const el = capRef.current;
|
|
77
|
+
if (!el) return;
|
|
78
|
+
|
|
79
|
+
const handleSolve = (e) => {
|
|
80
|
+
setCapToken(e.detail?.token || '');
|
|
81
|
+
};
|
|
82
|
+
const handleReset = () => {
|
|
83
|
+
setCapToken('');
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
el.addEventListener('solve', handleSolve);
|
|
87
|
+
el.addEventListener('reset', handleReset);
|
|
88
|
+
return () => {
|
|
89
|
+
el.removeEventListener('solve', handleSolve);
|
|
90
|
+
el.removeEventListener('reset', handleReset);
|
|
91
|
+
};
|
|
92
|
+
}, [pageReady, showPasswordLogin, isCaptchaEnabled, captchaProvider]);
|
|
93
|
+
|
|
94
|
+
// 2. Preload Fonts & Logo to prevent Layout Shifts (FOUC)
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
let isMounted = true;
|
|
97
|
+
|
|
98
|
+
const prepareAssets = async () => {
|
|
99
|
+
const tasks = [];
|
|
100
|
+
|
|
101
|
+
if (typeof document !== 'undefined' && document.fonts && typeof document.fonts.load === 'function') {
|
|
102
|
+
tasks.push(document.fonts.load('13px Manrope').catch(() => {}));
|
|
103
|
+
tasks.push(document.fonts.load('600 11px Manrope').catch(() => {}));
|
|
104
|
+
tasks.push(document.fonts.load('700 14px Manrope').catch(() => {}));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (typeof logo === 'string' && logo.trim()) {
|
|
108
|
+
const logoPromise = new Promise((resolve) => {
|
|
109
|
+
const img = new Image();
|
|
110
|
+
img.onload = resolve;
|
|
111
|
+
img.onerror = resolve;
|
|
112
|
+
img.src = logo;
|
|
113
|
+
if (typeof img.decode === 'function') {
|
|
114
|
+
img.decode().then(resolve).catch(resolve);
|
|
115
|
+
}
|
|
116
|
+
setTimeout(resolve, 300);
|
|
117
|
+
});
|
|
118
|
+
tasks.push(logoPromise);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await Promise.all(tasks);
|
|
122
|
+
if (isMounted) {
|
|
123
|
+
setPageReady(true);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
prepareAssets();
|
|
128
|
+
|
|
129
|
+
return () => {
|
|
130
|
+
isMounted = false;
|
|
131
|
+
};
|
|
132
|
+
}, [logo]);
|
|
133
|
+
|
|
134
|
+
// 3. Form Submission
|
|
135
|
+
const handleSubmit = async (e) => {
|
|
136
|
+
if (e && e.preventDefault) e.preventDefault();
|
|
137
|
+
if (loading) return;
|
|
138
|
+
|
|
139
|
+
if (!username.trim() || !password) {
|
|
140
|
+
setInternalError('Введите логин и пароль');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (isCaptchaEnabled && !capToken) {
|
|
145
|
+
setInternalError('Пожалуйста, подтвердите капчу');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setInternalError('');
|
|
150
|
+
setInternalLoading(true);
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (typeof onSubmit === 'function') {
|
|
154
|
+
await onSubmit({
|
|
155
|
+
username: username.trim(),
|
|
156
|
+
password,
|
|
157
|
+
captchaToken: isCaptchaEnabled ? capToken : undefined,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
setInternalError(err.message || 'Ошибка авторизации');
|
|
162
|
+
if (capRef.current && typeof capRef.current.reset === 'function') {
|
|
163
|
+
capRef.current.reset();
|
|
164
|
+
}
|
|
165
|
+
setCapToken('');
|
|
166
|
+
} finally {
|
|
167
|
+
setInternalLoading(false);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// 4. Passkey Action Handler
|
|
172
|
+
const handlePasskeyClick = async () => {
|
|
173
|
+
if (isPasskeyBusy) return;
|
|
174
|
+
setInternalError('');
|
|
175
|
+
setPasskeyInternalLoading(true);
|
|
176
|
+
try {
|
|
177
|
+
if (typeof onPasskeyLogin === 'function') {
|
|
178
|
+
await onPasskeyLogin();
|
|
179
|
+
}
|
|
180
|
+
} catch (err) {
|
|
181
|
+
setInternalError(err.message || 'Ошибка аутентификации через Passkey');
|
|
182
|
+
} finally {
|
|
183
|
+
setPasskeyInternalLoading(false);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (!pageReady) {
|
|
188
|
+
return (
|
|
189
|
+
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background }}>
|
|
190
|
+
<Spinner size={32} />
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const enabledSSO = Array.isArray(ssoProviders) ? ssoProviders : [];
|
|
196
|
+
const hasPasskey = showPasskey && typeof onPasskeyLogin === 'function';
|
|
197
|
+
const hasAuthMethods = showPasswordLogin || hasPasskey || enabledSSO.length > 0;
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background, padding: 16 }}>
|
|
201
|
+
<div style={{ width: '100%', maxWidth: cardMaxWidth }}>
|
|
202
|
+
{/* Header & Branding */}
|
|
203
|
+
<div style={{ textAlign: 'center', marginBottom: 28 }}>
|
|
204
|
+
{logo ? (
|
|
205
|
+
<div style={{ height: 54, display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' }}>
|
|
206
|
+
{typeof logo === 'string' ? (
|
|
207
|
+
<img src={logo} alt={logoAlt} style={{ maxHeight: 54, maxWidth: 200, objectFit: 'contain', display: 'block' }} />
|
|
208
|
+
) : (
|
|
209
|
+
logo
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
) : null}
|
|
213
|
+
|
|
214
|
+
{title && (
|
|
215
|
+
<h1 style={{ fontSize: '1.1rem', fontWeight: 700, color: 'var(--primary-text)', margin: 0 }}>
|
|
216
|
+
{title}
|
|
217
|
+
</h1>
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
{subtitle && (
|
|
221
|
+
<div style={{ fontSize: '.78rem', color: 'var(--muted)', marginTop: 4 }}>
|
|
222
|
+
{subtitle}
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{headerSlot}
|
|
228
|
+
|
|
229
|
+
{/* Notices */}
|
|
230
|
+
{notice && (
|
|
231
|
+
<div style={{ fontSize: '.78rem', color: 'var(--accent)', background: 'var(--accent-subtle, rgba(0,0,0,0.04))', padding: '8px 12px', borderRadius: 'var(--rads)', marginBottom: 14 }}>
|
|
232
|
+
{notice}
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{/* Errors */}
|
|
237
|
+
{error && (
|
|
238
|
+
<div style={{ fontSize: '.78rem', color: 'var(--danger)', background: 'var(--danger-bg)', padding: '8px 12px', borderRadius: 'var(--rads)', marginBottom: 14 }}>
|
|
239
|
+
{error}
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{/* Password Login Form */}
|
|
244
|
+
{showPasswordLogin && (
|
|
245
|
+
<div
|
|
246
|
+
onKeyDown={(e) => {
|
|
247
|
+
if (e.key === 'Enter') {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
handleSubmit();
|
|
250
|
+
}
|
|
251
|
+
}}
|
|
252
|
+
style={{ display: 'flex', flexDirection: 'column', gap: 14 }}
|
|
253
|
+
>
|
|
254
|
+
<Input
|
|
255
|
+
label={usernameLabel}
|
|
256
|
+
size="lg"
|
|
257
|
+
value={username}
|
|
258
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
259
|
+
placeholder={usernamePlaceholder}
|
|
260
|
+
name="login_auth_user"
|
|
261
|
+
id="login_auth_user"
|
|
262
|
+
autoComplete="off"
|
|
263
|
+
autoCorrect="off"
|
|
264
|
+
autoCapitalize="off"
|
|
265
|
+
spellCheck="false"
|
|
266
|
+
data-lpignore="true"
|
|
267
|
+
data-1p-ignore="true"
|
|
268
|
+
data-form-type="other"
|
|
269
|
+
data-bwignore="true"
|
|
270
|
+
required
|
|
271
|
+
/>
|
|
272
|
+
<Input
|
|
273
|
+
label={passwordLabel}
|
|
274
|
+
size="lg"
|
|
275
|
+
type="passwd"
|
|
276
|
+
name="login_auth_secret"
|
|
277
|
+
id="login_auth_secret"
|
|
278
|
+
autoComplete="off"
|
|
279
|
+
autoCorrect="off"
|
|
280
|
+
autoCapitalize="off"
|
|
281
|
+
spellCheck="false"
|
|
282
|
+
data-lpignore="true"
|
|
283
|
+
data-1p-ignore="true"
|
|
284
|
+
data-form-type="other"
|
|
285
|
+
data-bwignore="true"
|
|
286
|
+
value={password}
|
|
287
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
288
|
+
placeholder={passwordPlaceholder}
|
|
289
|
+
required
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
{/* Captcha Slot */}
|
|
293
|
+
{isCaptchaEnabled && (
|
|
294
|
+
<div style={{ display: 'flex', justifyContent: 'center', margin: '2px 0' }}>
|
|
295
|
+
{captchaProvider === 'cap' ? (
|
|
296
|
+
<cap-widget
|
|
297
|
+
ref={capRef}
|
|
298
|
+
data-cap-api-endpoint={captcha?.endpoint || '/api/captcha/'}
|
|
299
|
+
></cap-widget>
|
|
300
|
+
) : typeof captcha?.render === 'function' ? (
|
|
301
|
+
captcha.render(setCapToken, () => setCapToken(''), capRef)
|
|
302
|
+
) : null}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
{extraFields}
|
|
307
|
+
|
|
308
|
+
<Button
|
|
309
|
+
type="button"
|
|
310
|
+
variant="primary"
|
|
311
|
+
size="lg"
|
|
312
|
+
loading={loading}
|
|
313
|
+
onClick={handleSubmit}
|
|
314
|
+
style={{
|
|
315
|
+
width: '100%',
|
|
316
|
+
marginTop: 14,
|
|
317
|
+
color: '#ffffff',
|
|
318
|
+
background: 'var(--accent)',
|
|
319
|
+
fontWeight: 600,
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
{loading ? submitButtonLoadingText : submitButtonText}
|
|
323
|
+
</Button>
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
|
|
327
|
+
{/* Passkey Biometric Action */}
|
|
328
|
+
{hasPasskey && (
|
|
329
|
+
<div style={{ marginTop: showPasswordLogin ? 10 : 0 }}>
|
|
330
|
+
<Button
|
|
331
|
+
type="button"
|
|
332
|
+
variant="default"
|
|
333
|
+
size="lg"
|
|
334
|
+
loading={isPasskeyBusy}
|
|
335
|
+
onClick={handlePasskeyClick}
|
|
336
|
+
style={{
|
|
337
|
+
width: '100%',
|
|
338
|
+
color: 'var(--primary-text)',
|
|
339
|
+
background: 'var(--surface)',
|
|
340
|
+
border: '1px solid var(--border)',
|
|
341
|
+
fontWeight: 600,
|
|
342
|
+
fontSize: '.85rem',
|
|
343
|
+
}}
|
|
344
|
+
icon={passkeyIcon || <Fingerprint size={16} />}
|
|
345
|
+
>
|
|
346
|
+
{passkeyButtonText}
|
|
347
|
+
</Button>
|
|
348
|
+
</div>
|
|
349
|
+
)}
|
|
350
|
+
|
|
351
|
+
{/* SSO Divider */}
|
|
352
|
+
{(showPasswordLogin || hasPasskey) && enabledSSO.length > 0 && (
|
|
353
|
+
<Divider color="var(--border)" size="md" style={{ margin: '18px 0' }}>
|
|
354
|
+
{ssoDividerText}
|
|
355
|
+
</Divider>
|
|
356
|
+
)}
|
|
357
|
+
|
|
358
|
+
{/* SSO Providers */}
|
|
359
|
+
{enabledSSO.length > 0 && (
|
|
360
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
361
|
+
{enabledSSO.map((p) => {
|
|
362
|
+
const buttonNode = (
|
|
363
|
+
<Button
|
|
364
|
+
variant={p.variant || 'default'}
|
|
365
|
+
size="lg"
|
|
366
|
+
onClick={p.onClick}
|
|
367
|
+
style={{
|
|
368
|
+
width: '100%',
|
|
369
|
+
color: 'var(--primary-text)',
|
|
370
|
+
background: 'var(--surface)',
|
|
371
|
+
border: '1px solid var(--border)',
|
|
372
|
+
fontWeight: 600,
|
|
373
|
+
fontSize: '.85rem',
|
|
374
|
+
}}
|
|
375
|
+
icon={p.icon || <Lock size={15} />}
|
|
376
|
+
>
|
|
377
|
+
Войти через {p.name}
|
|
378
|
+
</Button>
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
if (p.url) {
|
|
382
|
+
return (
|
|
383
|
+
<a key={p.id} href={p.url} style={{ textDecoration: 'none', display: 'block' }}>
|
|
384
|
+
{buttonNode}
|
|
385
|
+
</a>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return <div key={p.id}>{buttonNode}</div>;
|
|
390
|
+
})}
|
|
391
|
+
</div>
|
|
392
|
+
)}
|
|
393
|
+
|
|
394
|
+
{/* Fallback when no auth methods available */}
|
|
395
|
+
{!hasAuthMethods && (
|
|
396
|
+
<div style={{ textAlign: 'center', color: 'var(--muted)', fontSize: '.78rem', padding: '20px 0' }}>
|
|
397
|
+
{noAuthMethodsText}
|
|
398
|
+
</div>
|
|
399
|
+
)}
|
|
400
|
+
|
|
401
|
+
{footerSlot}
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
405
|
+
}
|
package/ui/index.cjs
CHANGED
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
Input: () => Input,
|
|
50
50
|
Layout: () => Layout,
|
|
51
51
|
LinearProgress: () => LinearProgress,
|
|
52
|
+
LoginPage: () => LoginPage,
|
|
52
53
|
Modal: () => Modal,
|
|
53
54
|
PageShell: () => PageShell,
|
|
54
55
|
Pagination: () => Pagination,
|
|
@@ -125,7 +126,9 @@ var import_FilePicker = __toESM(require("./components/FilePicker.jsx"), 1);
|
|
|
125
126
|
var import_Divider = __toESM(require("./components/Divider.jsx"), 1);
|
|
126
127
|
var import_InfoBadge = __toESM(require("./components/InfoBadge.jsx"), 1);
|
|
127
128
|
var import_Layout = __toESM(require("./components/Layout.jsx"), 1);
|
|
129
|
+
var import_LoginPage = __toESM(require("./components/LoginPage.jsx"), 1);
|
|
128
130
|
const Layout = import_Layout.default;
|
|
131
|
+
const LoginPage = import_LoginPage.default;
|
|
129
132
|
const Button = import_Button.default;
|
|
130
133
|
const FilePicker = import_FilePicker.default;
|
|
131
134
|
const Divider = import_Divider.default;
|
package/ui/index.js
CHANGED
|
@@ -38,8 +38,10 @@ import { default as _FilePicker } from './components/FilePicker.jsx';
|
|
|
38
38
|
import { default as _Divider } from './components/Divider.jsx';
|
|
39
39
|
import { default as _InfoBadge } from './components/InfoBadge.jsx';
|
|
40
40
|
import { default as _Layout } from './components/Layout.jsx';
|
|
41
|
+
import { default as _LoginPage } from './components/LoginPage.jsx';
|
|
41
42
|
|
|
42
43
|
export const Layout = _Layout;
|
|
44
|
+
export const LoginPage = _LoginPage;
|
|
43
45
|
export const Button = _Button;
|
|
44
46
|
export const FilePicker = _FilePicker;
|
|
45
47
|
export const Divider = _Divider;
|
|
@@ -256,7 +256,7 @@ class StorageManager {
|
|
|
256
256
|
fileType,
|
|
257
257
|
mimeType,
|
|
258
258
|
fileSize: Number(f.file_size || 0),
|
|
259
|
-
cloudUrl: f.cloud_url,
|
|
259
|
+
// cloudUrl: f.cloud_url,
|
|
260
260
|
viewUrl: `${base}/cdn/${fileId}/view`,
|
|
261
261
|
downloadUrl: `${base}/cdn/${fileId}/download`,
|
|
262
262
|
url: `${base}/cdn/${fileId}`,
|
|
@@ -1016,7 +1016,7 @@ class StorageManager {
|
|
|
1016
1016
|
const fileType = rf.fileType || rf.file_type || (0, import_storage.getFileTypeCategory)(mimeType, rf.originalName || rf.original_name);
|
|
1017
1017
|
const originalName = rf.originalName || rf.original_name || "file";
|
|
1018
1018
|
const fileSize = Number(rf.fileSize || rf.file_size || 0);
|
|
1019
|
-
const cloudUrl = rf.cloudUrl || rf.cloud_url;
|
|
1019
|
+
const cloudUrl = rf.cloudUrl || rf.cloud_url || rf.url || (this.storageServerUrl ? `${this.storageServerUrl}/cdn/${fileId}` : "");
|
|
1020
1020
|
const cloudPublicId = rf.cloudPublicId || rf.cloud_public_id || fileId;
|
|
1021
1021
|
const storageId = rf.storageId || rf.storage_id || null;
|
|
1022
1022
|
if (this.query) {
|
|
@@ -265,7 +265,7 @@ export class StorageManager {
|
|
|
265
265
|
fileType,
|
|
266
266
|
mimeType,
|
|
267
267
|
fileSize: Number(f.file_size || 0),
|
|
268
|
-
cloudUrl: f.cloud_url,
|
|
268
|
+
// cloudUrl: f.cloud_url,
|
|
269
269
|
viewUrl: `${base}/cdn/${fileId}/view`,
|
|
270
270
|
downloadUrl: `${base}/cdn/${fileId}/download`,
|
|
271
271
|
url: `${base}/cdn/${fileId}`,
|
|
@@ -1135,7 +1135,7 @@ export class StorageManager {
|
|
|
1135
1135
|
const fileType = rf.fileType || rf.file_type || getFileTypeCategory(mimeType, rf.originalName || rf.original_name);
|
|
1136
1136
|
const originalName = rf.originalName || rf.original_name || 'file';
|
|
1137
1137
|
const fileSize = Number(rf.fileSize || rf.file_size || 0);
|
|
1138
|
-
const cloudUrl = rf.cloudUrl || rf.cloud_url;
|
|
1138
|
+
const cloudUrl = rf.cloudUrl || rf.cloud_url || rf.url || (this.storageServerUrl ? `${this.storageServerUrl}/cdn/${fileId}` : '');
|
|
1139
1139
|
const cloudPublicId = rf.cloudPublicId || rf.cloud_public_id || fileId;
|
|
1140
1140
|
const storageId = rf.storageId || rf.storage_id || null;
|
|
1141
1141
|
|