-
Notifications
You must be signed in to change notification settings - Fork 195
Expand file tree
/
Copy pathcommon.ts
More file actions
393 lines (352 loc) · 11.2 KB
/
Copy pathcommon.ts
File metadata and controls
393 lines (352 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
import {Terminal} from '@xterm/xterm';
import {Asset, TreeNode, User} from '@app/model';
import {SettingService} from '@app/services';
import {FitAddon} from '@xterm/addon-fit';
export function groupBy(array, f) {
const groups = {};
array.forEach(function (o) {
const group = JSON.stringify(f(o));
groups[group] = groups[group] || [];
groups[group].push(o);
});
return Object.keys(groups).map(function (group) {
return groups[group];
});
}
export function newTerminal(fontSize?: number) {
if (!fontSize || fontSize < 5 || fontSize > 50) {
fontSize = 13;
}
const ua = navigator.userAgent.toLowerCase();
let lineHeight = 1;
if (ua.indexOf('windows') !== -1) {
lineHeight = 1.2;
}
const term = new Terminal({
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
lineHeight: lineHeight,
fontSize: fontSize,
rightClickSelectsWord: true,
theme: {
background: '#1f1b1b'
}
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
return term;
}
export function setCookie(name, value, seconds) {
const d = new Date();
d.setTime(d.getTime() + seconds * 1000);
const expires = 'expires=' + d.toUTCString();
document.cookie = name + '=' + value + '; ' + expires;
}
export function getCookie(name: string): string {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
export function getCsrfTokenFromCookie(): string {
let prefix = getCookie('SESSION_COOKIE_NAME_PREFIX');
if (!prefix || [`""`, `''`].indexOf(prefix) > -1) {
prefix = '';
}
const name = `${prefix}csrftoken`;
return getCookie(name);
}
export function groupByProp(xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
}
export function getWaterMarkFields(user: User, asset: Asset) {
const userId = user.id;
const name = user.name;
const userName = user.username;
const assetId = asset.id;
const assetName = asset.name;
const assetAddress = asset.address;
const currentTime = formatDate(new Date());
return {userId, name, userName, assetId, assetName, assetAddress, currentTime};
}
export function getWaterMarkContent(user: User, asset: Asset, settingService: SettingService) {
const fields = getWaterMarkFields(user, asset);
const template = settingService.globalSetting.SECURITY_WATERMARK_SESSION_CONTENT || '';
// 找出模板中所有的变量占位符 ${xxx}
const placeholders: string[] = template.match(/\${([^}]+)}/g) || [];
const allVariables = {};
// 为模板中的每个变量准备值
placeholders.forEach(placeholder => {
const varName = placeholder.slice(2, -1); // 提取变量名,去掉 ${ 和 }
allVariables[varName] = fields[varName] !== undefined ? fields[varName] : 'N/A';
});
// 合并用户现有的字段和模板中可能缺失的字段
const safeFields = {...fields, ...allVariables};
// 安全解析模板
return new Function(...Object.keys(safeFields), `return \`${template}\`;`)(...Object.values(safeFields));
}
export function truncateCenter(s: string, l: number) {
if (s.length <= l) {
return s;
}
const centerIndex = Math.ceil(l / 2);
return s.slice(0, centerIndex - 2) + '...' + s.slice(centerIndex + 1, l);
}
function createWatermarkDiv(content, {
width = 300,
height = 300,
textAlign = 'center',
textBaseline = 'middle',
alpha = 0.3,
font = '20px monaco, microsoft yahei',
fillStyle = 'rgba(184, 184, 184, 0.8)',
rotate = 45,
zIndex = 1000,
lineHeight = 24
}) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
ctx.globalAlpha = 0.5;
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.textAlign = <CanvasTextAlign>textAlign;
ctx.textBaseline = <CanvasTextBaseline>textBaseline;
ctx.globalAlpha = alpha;
ctx.translate(0.5 * width, 0.5 * height);
ctx.rotate(-(rotate * Math.PI) / 180);
function generateMultiLineText(_ctx: CanvasRenderingContext2D, _text: string, _width: number, _lineHeight: number) {
const words = _text.split('\n');
let line = '';
const x = 0;
let y = 0;
for (let n = 0; n < words.length; n++) {
line = words[n];
// line = truncateCenter(line, 64);
_ctx.fillText(line, x, y);
y += _lineHeight;
}
}
generateMultiLineText(ctx, content, width, lineHeight);
const base64Url = canvas.toDataURL();
const watermarkDiv = document.createElement('div');
const styles = {
position: 'absolute',
display: 'block',
visibility: 'visible',
top: 0,
left: 0,
width: '100%',
height: '100%',
opacity: 1,
'z-index': zIndex,
'pointer-events': 'none',
'background-repeat': 'repeat',
'background-image': `url('${base64Url}')`
};
const layoutStyles = ['margin', 'padding', 'border'];
const directionStyles = ['top', 'right', 'bottom', 'left'];
layoutStyles.forEach(lay => {
directionStyles.forEach(direction => {
const key = `${lay}-${direction}`;
styles[key] = '0';
});
});
const style = Object.keys(styles)
.reduce((prev, key) => {
return `${prev}${key}:${styles[key]};`;
}, '');
watermarkDiv.setAttribute('style', style);
watermarkDiv.classList.add('watermark');
return {watermark: watermarkDiv, base64: base64Url};
}
export function canvasWaterMark({
// 使用 ES6 的函数默认值方式设置参数的默认取值
// 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters
container = document.body,
content = 'JumpServer',
settings = {}
} = {}) {
container.style.position = 'relative';
const res = createWatermarkDiv(content, settings);
const watermarkDiv = res.watermark;
container.insertBefore(watermarkDiv, container.firstChild);
// 监听 dom 节点的 style 属性变化
const observer = new MutationObserver(mutations => {
setTimeout(() => {
container.removeChild(container.firstChild);
// 这里不用再新建了,因为下面监听了 container 的子节点变化,会重新创建的
// canvasWaterMark({container, content, settings});
}, 100);
});
observer.observe(watermarkDiv, {childList: false, attributes: true, subtree: false});
const containerObserver = new MutationObserver(mutations => {
const removed = mutations.filter(m => m.type === 'childList' && m.removedNodes.length > 0);
if (removed.length === 0) {
return;
}
const removedNodes = removed[0].removedNodes;
if (removedNodes.length === 0) {
return;
}
const removedHtml = removedNodes[0]['outerHTML'];
if (removedHtml.indexOf(res.base64) < 0) {
return;
}
setTimeout(() => {
canvasWaterMark({container, content, settings});
}, 100);
});
containerObserver.observe(container, {childList: true, attributes: false, subtree: false});
}
export function windowOpen(url) {
const a = document.createElement('a');
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
}
export function zeroPad(num, minLength) {
let str = num.toString();
// Add leading zeroes until string is long enough
while (str.length < minLength) {
str = '0' + str;
}
return str;
}
export function formatTimeWithSeconds(seconds) {
let hour = 0, minute = 0, second = 0;
const ref = [3600, 60, 1];
for (let i = 0; i < ref.length; i++) {
const val = ref[i];
while (val <= seconds) {
seconds -= val;
switch (i) {
case 0:
hour++;
break;
case 1:
minute++;
break;
case 2:
second++;
break;
}
}
}
return [hour, minute, second];
}
export function formatTime(millis: number) {
const totalSeconds = millis / 1000;
const [hour, minute, second] = formatTimeWithSeconds(totalSeconds);
let time = zeroPad(minute, 2) + ':' + zeroPad(second, 2);
if (hour > 0) {
time = zeroPad(hour, 2) + ':' + time;
}
return time;
}
export function formatDate(date: Date) {
const pad = (n) => n.toString().padStart(2, '0');
const year = date.getFullYear();
const month = pad(date.getMonth() + 1); // 月份从0开始
const day = pad(date.getDate());
const hours = pad(date.getHours());
const minutes = pad(date.getMinutes());
const seconds = pad(date.getSeconds());
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
/**
* 判断用户有没有下载jumpServer
* @param {string} url
* @param {Function} fail 失败的回调
*/
export function launchLocalApp(url, fail) {
if (!url) {
return;
}
let isDone = false;
let decideTimeOut = null;
const aLink = document.createElement('iframe');
aLink.style.display = 'none';
aLink.src = url;
document.body.appendChild(aLink);
window.onblur = () => {
if (decideTimeOut) {
isDone = true;
}
};
const curDone = function done() {
isDone = false;
clearTimeout(decideTimeOut);
decideTimeOut = null;
document.body.removeChild(aLink);
};
decideTimeOut = setTimeout(() => {
if (isDone) {
curDone();
} else {
fail();
curDone();
}
}, 2000);
}
/**
* 打开新页卡
* @param {Object} node
* @param {String} newWindowMode
*/
export function connectOnNewPage(node: TreeNode, newWindowMode?: string) {
const url = `/luna/connect?login_to=${node.id}&type=${node.meta.type}`;
let params = 'toolbar=yes,scrollbars=yes,resizable=yes';
// auto 模式:尺寸为屏幕的三分之一并根据会话内的计数在屏幕上“级联/平铺”位置
if (newWindowMode === 'auto') {
let count: number;
let top = 50;
count = parseInt(window.sessionStorage.getItem('newWindowCount'), 10);
if (isNaN(count)) {
count = 0;
}
let left = 100 + count * 100;
top = 50 + count * 50;
if (left + screen.width / 3 > screen.width) {
// 支持两排足以
top = screen.height / 3;
count = 1;
left = 100;
}
params = params + `,top=${top},left=${left},width=${screen.width / 3},height=${screen.height / 3}`;
window.sessionStorage.setItem('newWindowCount', `${count + 1}`);
window.open(url, '_blank', params);
// auto 模式:尺寸为当前窗口的 innerWidth x innerHeight;位置固定在 top=50,left=100
} else if (newWindowMode === 'new') {
params = params + `,top=50,left=100,width=${window.innerWidth},height=${window.innerHeight}`;
window.open(url, '_blank', params);
} else {
window.open(url, '_blank');
}
}
export function getQueryParamFromURL(queryKey) {
let result = null,
tmp = [];
location.search
.substr(1)
.split('&')
.forEach(function (item) {
tmp = item.split('=');
if (tmp[0] === queryKey) {
result = tmp[1];
}
});
return result;
}