ultimate-jekyll-manager 0.0.197 → 0.0.199
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/README.md +0 -2
- package/TODO-ATTRIBUTION2.md +753 -0
- package/dist/assets/js/core/auth.js +30 -32
- package/dist/assets/js/core/query-strings.js +16 -12
- package/dist/assets/js/pages/account/sections/api-keys.js +1 -4
- package/dist/assets/js/pages/account/sections/connections.js +11 -17
- package/dist/assets/js/pages/account/sections/delete.js +4 -7
- package/dist/assets/js/pages/account/sections/security.js +2 -6
- package/dist/assets/js/pages/admin/notifications/new/index.js +5 -16
- package/dist/assets/js/pages/download/index.js +3 -6
- package/dist/assets/js/pages/oauth2/index.js +5 -5
- package/dist/assets/js/pages/payment/checkout/modules/session.js +1 -1
- package/dist/assets/js/pages/status/index.js +152 -0
- package/dist/assets/js/pages/token/index.js +1 -5
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/status.html +93 -0
- package/dist/defaults/src/_config.yml +10 -6
- package/package.json +7 -7
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
2
2
|
|
|
3
|
+
// Constants
|
|
4
|
+
const SIGNUP_MAX_AGE = 5 * 60 * 1000;
|
|
5
|
+
|
|
3
6
|
// Auth Module
|
|
4
7
|
export default function (Manager, options) {
|
|
5
8
|
// Shortcuts
|
|
@@ -45,22 +48,8 @@ export default function (Manager, options) {
|
|
|
45
48
|
if (user) {
|
|
46
49
|
// User is authenticated
|
|
47
50
|
|
|
48
|
-
// Check if this is a new user account (created in last 5 minutes) and send metadata
|
|
49
|
-
const accountAge = Date.now() - new Date(user.metadata.creationTime).getTime();
|
|
50
|
-
const fiveMinutes = 5 * 60 * 1000;
|
|
51
|
-
|
|
52
|
-
/* @dev-only:start */
|
|
53
|
-
{
|
|
54
|
-
// Log account age for debugging
|
|
55
|
-
const ageInMinutes = Math.floor(accountAge / 1000 / 60);
|
|
56
|
-
console.log('[Auth] Account age:', ageInMinutes, 'minutes');
|
|
57
|
-
}
|
|
58
|
-
/* @dev-only:end */
|
|
59
|
-
|
|
60
51
|
// Send user signup metadata if account is new
|
|
61
|
-
|
|
62
|
-
await sendUserSignupMetadata(user, webManager);
|
|
63
|
-
}
|
|
52
|
+
await sendUserSignupMetadata(user, webManager);
|
|
64
53
|
|
|
65
54
|
// Check if page requires user to be unauthenticated (e.g., signin page)
|
|
66
55
|
if (policy === 'unauthenticated') {
|
|
@@ -167,45 +156,54 @@ function setAnalyticsUserId(user, webManager) {
|
|
|
167
156
|
// Send user metadata to server (affiliate, UTM params, etc.)
|
|
168
157
|
async function sendUserSignupMetadata(user, webManager) {
|
|
169
158
|
try {
|
|
170
|
-
//
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
|
|
159
|
+
// Check if this is a new user account (created in last 5 minutes)
|
|
160
|
+
const accountAge = Date.now() - new Date(user.metadata.creationTime).getTime();
|
|
161
|
+
const signupProcessed = webManager.storage().get('flags.signupProcessed', false);
|
|
162
|
+
|
|
163
|
+
/* @dev-only:start */
|
|
164
|
+
{
|
|
165
|
+
// Log account age for debugging
|
|
166
|
+
const ageInMinutes = Math.floor(accountAge / 1000 / 60);
|
|
167
|
+
console.log('[Auth] Account age:', ageInMinutes, 'minutes, signupProcessed:', signupProcessed);
|
|
168
|
+
}
|
|
169
|
+
/* @dev-only:end */
|
|
174
170
|
|
|
175
|
-
// Only proceed if we haven't sent signup metadata yet
|
|
176
|
-
if (
|
|
171
|
+
// Only proceed if account is new and we haven't sent signup metadata yet
|
|
172
|
+
if (accountAge >= SIGNUP_MAX_AGE || signupProcessed) {
|
|
177
173
|
return;
|
|
178
174
|
}
|
|
179
175
|
|
|
176
|
+
// Get attribution data from storage
|
|
177
|
+
const attribution = webManager.storage().get('attribution', {});
|
|
178
|
+
|
|
180
179
|
// Build the payload
|
|
181
180
|
const payload = {
|
|
182
|
-
//
|
|
183
|
-
affiliateCode:
|
|
181
|
+
// Legacy support (can be removed once all servers are updated)
|
|
182
|
+
affiliateCode: attribution.affiliate?.code || '',
|
|
184
183
|
|
|
185
184
|
// New structure
|
|
186
|
-
|
|
187
|
-
utmData: utmData || {},
|
|
185
|
+
attribution: attribution,
|
|
188
186
|
context: webManager.utilities().getContext(),
|
|
189
187
|
};
|
|
190
188
|
|
|
191
189
|
// Get server API URL
|
|
192
|
-
const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
|
|
190
|
+
const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/signup`;
|
|
191
|
+
|
|
192
|
+
// Log
|
|
193
|
+
console.log('[Auth] Sending user metadata:', payload);
|
|
193
194
|
|
|
194
195
|
// Make API call to send signup metadata
|
|
195
196
|
const response = await authorizedFetch(serverApiURL, {
|
|
196
197
|
method: 'POST',
|
|
197
198
|
response: 'json',
|
|
198
|
-
body:
|
|
199
|
-
command: 'user:sign-up',
|
|
200
|
-
payload: payload
|
|
201
|
-
}
|
|
199
|
+
body: payload,
|
|
202
200
|
});
|
|
203
201
|
|
|
204
202
|
// Log
|
|
205
203
|
console.log('[Auth] User metadata sent successfully:', response);
|
|
206
204
|
|
|
207
|
-
// Mark signup as sent (keep the
|
|
208
|
-
webManager.storage().set('
|
|
205
|
+
// Mark signup as sent (keep the attribution data for reference)
|
|
206
|
+
webManager.storage().set('flags.signupProcessed', true);
|
|
209
207
|
} catch (error) {
|
|
210
208
|
console.error('[Auth] Error sending user metadata:', error);
|
|
211
209
|
// Don't throw - we don't want to block the signup flow
|
|
@@ -12,14 +12,22 @@ export default function (Manager, options) {
|
|
|
12
12
|
// Get URL parameters
|
|
13
13
|
const urlParams = new URLSearchParams(window.location.search);
|
|
14
14
|
|
|
15
|
+
// Get current attribution data
|
|
16
|
+
const attribution = webManager.storage().get('attribution', {});
|
|
17
|
+
|
|
15
18
|
// Process affiliate/referral parameters
|
|
16
|
-
processAffiliateParams(urlParams);
|
|
19
|
+
processAffiliateParams(urlParams, attribution);
|
|
17
20
|
|
|
18
21
|
// Process UTM parameters
|
|
19
|
-
processUTMParams(urlParams);
|
|
22
|
+
processUTMParams(urlParams, attribution);
|
|
23
|
+
|
|
24
|
+
// Save updated attribution if anything changed
|
|
25
|
+
if (Object.keys(attribution).length > 0) {
|
|
26
|
+
webManager.storage().set('attribution', attribution);
|
|
27
|
+
}
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
function processAffiliateParams(urlParams) {
|
|
30
|
+
function processAffiliateParams(urlParams, attribution) {
|
|
23
31
|
// Check for aff or ref parameter
|
|
24
32
|
const affParam = urlParams.get('aff') || urlParams.get('ref');
|
|
25
33
|
|
|
@@ -28,19 +36,16 @@ export default function (Manager, options) {
|
|
|
28
36
|
return;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
// Save to
|
|
32
|
-
|
|
39
|
+
// Save affiliate data to attribution object
|
|
40
|
+
attribution.affiliate = {
|
|
33
41
|
code: affParam,
|
|
34
42
|
timestamp: new Date().toISOString(),
|
|
35
43
|
url: window.location.href,
|
|
36
44
|
page: window.location.pathname
|
|
37
45
|
};
|
|
38
|
-
|
|
39
|
-
// Use webManager storage to save affiliate data
|
|
40
|
-
webManager.storage().set('marketing.affiliate', affiliateData);
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
function processUTMParams(urlParams) {
|
|
48
|
+
function processUTMParams(urlParams, attribution) {
|
|
44
49
|
// Define UTM parameters to capture
|
|
45
50
|
const utmParams = [
|
|
46
51
|
'utm_source',
|
|
@@ -69,12 +74,11 @@ export default function (Manager, options) {
|
|
|
69
74
|
return;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
// Add metadata
|
|
77
|
+
// Add metadata and save to attribution object
|
|
73
78
|
utmData.timestamp = new Date().toISOString();
|
|
74
79
|
utmData.url = window.location.href;
|
|
75
80
|
utmData.page = window.location.pathname;
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
webManager.storage().set('marketing.utm', utmData);
|
|
82
|
+
attribution.utm = utmData;
|
|
79
83
|
}
|
|
80
84
|
}
|
|
@@ -65,7 +65,7 @@ function setupResetApiKeyForm() {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Get server API URL
|
|
68
|
-
const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
|
|
68
|
+
const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/api-keys`;
|
|
69
69
|
|
|
70
70
|
// Make API call to reset API key
|
|
71
71
|
const response = await authorizedFetch(serverApiURL, {
|
|
@@ -73,9 +73,6 @@ function setupResetApiKeyForm() {
|
|
|
73
73
|
timeout: 30000,
|
|
74
74
|
response: 'json',
|
|
75
75
|
tries: 2,
|
|
76
|
-
body: {
|
|
77
|
-
command: 'user:regenerate-api-keys',
|
|
78
|
-
},
|
|
79
76
|
});
|
|
80
77
|
|
|
81
78
|
if (!response.privateKey) {
|
|
@@ -16,7 +16,7 @@ const supportedProviders = ['google', 'discord', 'github', 'twitter', 'facebook'
|
|
|
16
16
|
|
|
17
17
|
// Get API URL helper
|
|
18
18
|
function getApiUrl() {
|
|
19
|
-
return `${webManager.getApiUrl()}/backend-manager`;
|
|
19
|
+
return `${webManager.getApiUrl()}/backend-manager/user/oauth2`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Initialize connections section
|
|
@@ -295,14 +295,11 @@ async function handleConnect(providerId) {
|
|
|
295
295
|
response: 'json',
|
|
296
296
|
tries: 2,
|
|
297
297
|
body: {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
scope: scope,
|
|
304
|
-
referrer: window.location.href,
|
|
305
|
-
},
|
|
298
|
+
redirect: false,
|
|
299
|
+
provider: providerId,
|
|
300
|
+
state: 'authorize',
|
|
301
|
+
scope: scope,
|
|
302
|
+
referrer: window.location.href,
|
|
306
303
|
},
|
|
307
304
|
});
|
|
308
305
|
|
|
@@ -338,14 +335,11 @@ async function handleDisconnect(providerId) {
|
|
|
338
335
|
response: 'json',
|
|
339
336
|
tries: 2,
|
|
340
337
|
body: {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
scope: scope,
|
|
347
|
-
referrer: window.location.href,
|
|
348
|
-
},
|
|
338
|
+
redirect: false,
|
|
339
|
+
provider: providerId,
|
|
340
|
+
state: 'deauthorize',
|
|
341
|
+
scope: scope,
|
|
342
|
+
referrer: window.location.href,
|
|
349
343
|
},
|
|
350
344
|
});
|
|
351
345
|
|
|
@@ -70,17 +70,14 @@ function setupDeleteAccountForm() {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Send delete request to server
|
|
73
|
-
const response = await authorizedFetch(webManager.getApiUrl()
|
|
74
|
-
method: '
|
|
73
|
+
const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user`, {
|
|
74
|
+
method: 'DELETE',
|
|
75
75
|
timeout: 30000,
|
|
76
76
|
response: 'json',
|
|
77
77
|
tries: 2,
|
|
78
78
|
body: {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
reason: data.reason || '',
|
|
82
|
-
confirmed: true,
|
|
83
|
-
},
|
|
79
|
+
reason: data.reason || '',
|
|
80
|
+
confirmed: true,
|
|
84
81
|
},
|
|
85
82
|
});
|
|
86
83
|
|
|
@@ -198,17 +198,13 @@ async function updateActiveSessions(account) {
|
|
|
198
198
|
|
|
199
199
|
// Fetch other active sessions from server
|
|
200
200
|
try {
|
|
201
|
-
const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
|
|
201
|
+
const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/sessions`;
|
|
202
202
|
|
|
203
203
|
const data = await authorizedFetch(serverApiURL, {
|
|
204
|
-
method: '
|
|
204
|
+
method: 'GET',
|
|
205
205
|
timeout: 60000,
|
|
206
206
|
response: 'json',
|
|
207
207
|
tries: 2,
|
|
208
|
-
body: {
|
|
209
|
-
command: 'user:get-active-sessions',
|
|
210
|
-
payload: {},
|
|
211
|
-
},
|
|
212
208
|
});
|
|
213
209
|
|
|
214
210
|
// Process sessions from server response
|
|
@@ -240,7 +240,7 @@ function transformDataForAPI(formData) {
|
|
|
240
240
|
|
|
241
241
|
// Send notification via API
|
|
242
242
|
async function sendNotification(payload) {
|
|
243
|
-
const functionsUrl = getAPIFunctionsUrl()
|
|
243
|
+
const functionsUrl = `${getAPIFunctionsUrl()}/admin/notification`;
|
|
244
244
|
|
|
245
245
|
return authorizedFetch(functionsUrl, {
|
|
246
246
|
method: 'POST',
|
|
@@ -248,28 +248,19 @@ async function sendNotification(payload) {
|
|
|
248
248
|
response: 'json',
|
|
249
249
|
tries: 1,
|
|
250
250
|
log: true,
|
|
251
|
-
body:
|
|
252
|
-
command: 'admin:send-notification',
|
|
253
|
-
payload: payload,
|
|
254
|
-
},
|
|
251
|
+
body: payload,
|
|
255
252
|
});
|
|
256
253
|
}
|
|
257
254
|
|
|
258
255
|
// Fetch user statistics
|
|
259
256
|
async function fetchUserStats() {
|
|
260
257
|
try {
|
|
261
|
-
const response = await authorizedFetch(getAPIFunctionsUrl()
|
|
262
|
-
method: '
|
|
258
|
+
const response = await authorizedFetch(`${getAPIFunctionsUrl()}/admin/firestore?path=meta/stats`, {
|
|
259
|
+
method: 'GET',
|
|
263
260
|
timeout: 60000,
|
|
264
261
|
response: 'json',
|
|
265
262
|
tries: 2,
|
|
266
263
|
log: true,
|
|
267
|
-
body: {
|
|
268
|
-
command: 'admin:firestore-read',
|
|
269
|
-
payload: {
|
|
270
|
-
path: 'meta/stats',
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
264
|
});
|
|
274
265
|
|
|
275
266
|
// Extract user count from response
|
|
@@ -477,9 +468,7 @@ function clearAutoSubmitCountdown() {
|
|
|
477
468
|
|
|
478
469
|
// Get API functions URL
|
|
479
470
|
function getAPIFunctionsUrl() {
|
|
480
|
-
|
|
481
|
-
//@TODO remove this when fixed api url
|
|
482
|
-
return `${webManager.getFunctionsUrl('prod')}/bm_api`;
|
|
471
|
+
return `${webManager.getApiUrl()}/backend-manager`;
|
|
483
472
|
}
|
|
484
473
|
|
|
485
474
|
// Tracking functions
|
|
@@ -196,17 +196,14 @@ function setupMobileEmailForms() {
|
|
|
196
196
|
console.log('Mobile email form submitted:', { platform, email: data.email });
|
|
197
197
|
|
|
198
198
|
// Get API endpoint
|
|
199
|
-
const apiEndpoint = `${webManager.getApiUrl()}/backend-manager`;
|
|
199
|
+
const apiEndpoint = `${webManager.getApiUrl()}/backend-manager/general/email`;
|
|
200
200
|
|
|
201
201
|
// Send request using wonderful-fetch
|
|
202
202
|
await fetch(apiEndpoint, {
|
|
203
203
|
method: 'POST',
|
|
204
204
|
body: {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
id: 'general:download-app-link',
|
|
208
|
-
email: data.email,
|
|
209
|
-
},
|
|
205
|
+
id: 'general:download-app-link',
|
|
206
|
+
email: data.email,
|
|
210
207
|
},
|
|
211
208
|
response: 'json',
|
|
212
209
|
timeout: 30000,
|
|
@@ -100,16 +100,16 @@ async function handleOAuthCallback() {
|
|
|
100
100
|
|
|
101
101
|
console.log('Tokenize payload:', payload);
|
|
102
102
|
|
|
103
|
+
// Build the RESTful URL (replace /backend-manager with /backend-manager/user/oauth2)
|
|
104
|
+
const serverUrl = stateParsed.serverUrl.replace(/\/backend-manager\/?$/, '/backend-manager/user/oauth2');
|
|
105
|
+
|
|
103
106
|
// Call server to complete OAuth flow
|
|
104
|
-
const response = await authorizedFetch(
|
|
107
|
+
const response = await authorizedFetch(serverUrl, {
|
|
105
108
|
method: 'POST',
|
|
106
109
|
timeout: 60000,
|
|
107
110
|
response: 'json',
|
|
108
111
|
tries: 2,
|
|
109
|
-
body:
|
|
110
|
-
command: 'user:oauth2',
|
|
111
|
-
payload: payload
|
|
112
|
-
}
|
|
112
|
+
body: payload,
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
console.log('Tokenize response:', response);
|
|
@@ -44,7 +44,7 @@ export function buildPaymentIntentData(webManager) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// Get UTM parameters from storage
|
|
47
|
-
const utmData = webManager.storage().get('
|
|
47
|
+
const utmData = webManager.storage().get('attribution.utm');
|
|
48
48
|
let utm = {};
|
|
49
49
|
|
|
50
50
|
// Check if stored UTM data exists and is less than 30 days old
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
// Libraries
|
|
6
6
|
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
7
|
+
import fetch from 'wonderful-fetch';
|
|
7
8
|
|
|
8
9
|
let webManager = null;
|
|
9
10
|
|
|
@@ -21,6 +22,7 @@ export default (Manager) => {
|
|
|
21
22
|
initializeSubscribeForm();
|
|
22
23
|
initializeTooltips();
|
|
23
24
|
initializeRefreshTimer();
|
|
25
|
+
initializeBuildInfo();
|
|
24
26
|
|
|
25
27
|
// Fetch status data (if configured)
|
|
26
28
|
fetchStatusData();
|
|
@@ -41,6 +43,14 @@ const config = {
|
|
|
41
43
|
incidentsList: '#incidents-list',
|
|
42
44
|
incidentsEmpty: '#incidents-empty',
|
|
43
45
|
subscribeForm: '#status-subscribe-form',
|
|
46
|
+
buildInfoLoading: '#build-info-loading',
|
|
47
|
+
buildInfoContent: '#build-info-content',
|
|
48
|
+
buildInfoError: '#build-info-error',
|
|
49
|
+
buildTime: '#build-time',
|
|
50
|
+
buildTimeAgo: '#build-time-ago',
|
|
51
|
+
buildEnvironment: '#build-environment',
|
|
52
|
+
buildPackages: '#build-packages',
|
|
53
|
+
buildRepo: '#build-repo',
|
|
44
54
|
},
|
|
45
55
|
// Maps status levels to Bootstrap bg-* classes
|
|
46
56
|
statusClasses: {
|
|
@@ -234,6 +244,148 @@ function initializeRefreshTimer() {
|
|
|
234
244
|
}, 1000);
|
|
235
245
|
}
|
|
236
246
|
|
|
247
|
+
// Initialize build info section
|
|
248
|
+
async function initializeBuildInfo() {
|
|
249
|
+
const $loading = document.querySelector(config.selectors.buildInfoLoading);
|
|
250
|
+
const $content = document.querySelector(config.selectors.buildInfoContent);
|
|
251
|
+
const $error = document.querySelector(config.selectors.buildInfoError);
|
|
252
|
+
|
|
253
|
+
if (!$loading || !$content) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
// Fetch build.json
|
|
259
|
+
const data = await fetch(`${window.location.origin}/build.json`, {
|
|
260
|
+
response: 'json',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
console.log('Build info data:', data);
|
|
264
|
+
|
|
265
|
+
// Normalize the data structure
|
|
266
|
+
const buildData = {
|
|
267
|
+
timestamp: data.timestamp || null,
|
|
268
|
+
environment: data.environment || (data.serving ? 'development' : 'production'),
|
|
269
|
+
packages: data.packages || null,
|
|
270
|
+
repo: data.repo || null,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Test older dates
|
|
274
|
+
// buildData.timestamp = Date.now() - 1000 * 60 * 60 * 24 * 7;
|
|
275
|
+
|
|
276
|
+
displayBuildInfo(buildData);
|
|
277
|
+
$loading.classList.add('d-none');
|
|
278
|
+
$content.classList.remove('d-none');
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('Failed to load build info:', error);
|
|
281
|
+
$loading.classList.add('d-none');
|
|
282
|
+
if ($error) {
|
|
283
|
+
$error.classList.remove('d-none');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Display build information
|
|
289
|
+
function displayBuildInfo(data) {
|
|
290
|
+
const $time = document.querySelector(config.selectors.buildTime);
|
|
291
|
+
const $timeAgo = document.querySelector(config.selectors.buildTimeAgo);
|
|
292
|
+
const $environment = document.querySelector(config.selectors.buildEnvironment);
|
|
293
|
+
const $packages = document.querySelector(config.selectors.buildPackages);
|
|
294
|
+
const $repo = document.querySelector(config.selectors.buildRepo);
|
|
295
|
+
|
|
296
|
+
// Build time
|
|
297
|
+
if ($time && data.timestamp) {
|
|
298
|
+
const buildDate = new Date(data.timestamp);
|
|
299
|
+
$time.textContent = buildDate.toLocaleString('en-US', {
|
|
300
|
+
weekday: 'short',
|
|
301
|
+
year: 'numeric',
|
|
302
|
+
month: 'short',
|
|
303
|
+
day: 'numeric',
|
|
304
|
+
hour: 'numeric',
|
|
305
|
+
minute: '2-digit',
|
|
306
|
+
hour12: true,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Time ago (and keep it updated every second)
|
|
311
|
+
if ($timeAgo && data.timestamp) {
|
|
312
|
+
updateTimeAgo($timeAgo, data.timestamp);
|
|
313
|
+
setInterval(() => updateTimeAgo($timeAgo, data.timestamp), 1000);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Environment
|
|
317
|
+
if ($environment && data.environment) {
|
|
318
|
+
const envText = data.environment.charAt(0).toUpperCase() + data.environment.slice(1);
|
|
319
|
+
const envClass = data.environment === 'production' ? 'text-success' : 'text-warning';
|
|
320
|
+
$environment.innerHTML = `<span class="${envClass}">${escapeHtml(envText)}</span>`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Packages
|
|
324
|
+
if ($packages && data.packages) {
|
|
325
|
+
const packageBadges = Object.entries(data.packages)
|
|
326
|
+
.map(([name, version]) => {
|
|
327
|
+
return `<span class="badge bg-body-secondary text-body fw-normal">${escapeHtml(name)}: <span class="fw-semibold">${escapeHtml(version)}</span></span>`;
|
|
328
|
+
})
|
|
329
|
+
.join('');
|
|
330
|
+
$packages.innerHTML = packageBadges;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Repository
|
|
334
|
+
if ($repo && data.repo) {
|
|
335
|
+
const repoUrl = `https://github.com/${data.repo.user}/${data.repo.name}`;
|
|
336
|
+
$repo.innerHTML = `<a href="${repoUrl}" target="_blank" rel="noopener noreferrer" class="text-decoration-none">${escapeHtml(data.repo.user)}/${escapeHtml(data.repo.name)}</a>`;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Update relative time display
|
|
341
|
+
function updateTimeAgo($element, timestamp) {
|
|
342
|
+
const buildDate = new Date(timestamp);
|
|
343
|
+
const now = new Date();
|
|
344
|
+
const diffMs = now - buildDate;
|
|
345
|
+
const totalSeconds = Math.floor(diffMs / 1000);
|
|
346
|
+
|
|
347
|
+
if (totalSeconds <= 0) {
|
|
348
|
+
$element.textContent = 'Just now';
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Calculate each unit
|
|
353
|
+
const years = Math.floor(totalSeconds / (365 * 24 * 60 * 60));
|
|
354
|
+
const months = Math.floor((totalSeconds % (365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60));
|
|
355
|
+
const days = Math.floor((totalSeconds % (30 * 24 * 60 * 60)) / (24 * 60 * 60));
|
|
356
|
+
const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
|
|
357
|
+
const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
|
|
358
|
+
const seconds = totalSeconds % 60;
|
|
359
|
+
|
|
360
|
+
// Build parts - once we start, include all remaining units
|
|
361
|
+
const parts = [];
|
|
362
|
+
let started = false;
|
|
363
|
+
|
|
364
|
+
if (years > 0) {
|
|
365
|
+
parts.push(`${years}y`);
|
|
366
|
+
started = true;
|
|
367
|
+
}
|
|
368
|
+
if (started || months > 0) {
|
|
369
|
+
parts.push(`${months}mo`);
|
|
370
|
+
started = true;
|
|
371
|
+
}
|
|
372
|
+
if (started || days > 0) {
|
|
373
|
+
parts.push(`${days}d`);
|
|
374
|
+
started = true;
|
|
375
|
+
}
|
|
376
|
+
if (started || hours > 0) {
|
|
377
|
+
parts.push(`${hours}h`);
|
|
378
|
+
started = true;
|
|
379
|
+
}
|
|
380
|
+
if (started || minutes > 0) {
|
|
381
|
+
parts.push(`${minutes}m`);
|
|
382
|
+
started = true;
|
|
383
|
+
}
|
|
384
|
+
parts.push(`${seconds}s`);
|
|
385
|
+
|
|
386
|
+
$element.textContent = parts.join(' ') + ' ago';
|
|
387
|
+
}
|
|
388
|
+
|
|
237
389
|
// Fetch status data from API (if configured)
|
|
238
390
|
function fetchStatusData() {
|
|
239
391
|
// Check if there's a status API endpoint configured
|
|
@@ -68,17 +68,13 @@ export default function (Manager) {
|
|
|
68
68
|
|
|
69
69
|
// Generate custom token via backend-manager API
|
|
70
70
|
async function generateCustomToken(webManager) {
|
|
71
|
-
const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
|
|
71
|
+
const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/token`;
|
|
72
72
|
|
|
73
73
|
const response = await authorizedFetch(serverApiURL, {
|
|
74
74
|
method: 'POST',
|
|
75
75
|
timeout: 60000,
|
|
76
76
|
response: 'json',
|
|
77
77
|
tries: 2,
|
|
78
|
-
body: {
|
|
79
|
-
command: 'user:create-custom-token',
|
|
80
|
-
payload: {},
|
|
81
|
-
},
|
|
82
78
|
});
|
|
83
79
|
|
|
84
80
|
// Extract token from response
|