befly 3.19.0 → 3.19.6

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.
@@ -97,7 +97,6 @@ export default {
97
97
  id: admin.data.id,
98
98
  username: admin.data.username,
99
99
  nickname: admin.data.nickname,
100
- state: admin.data.state,
101
100
  roleCode: admin.data.roleCode,
102
101
  roleType: admin.data.roleType,
103
102
  loginAt: Date.now()
@@ -114,7 +113,6 @@ export default {
114
113
  id: sessionData.id,
115
114
  username: sessionData.username,
116
115
  nickname: sessionData.nickname,
117
- state: sessionData.state,
118
116
  roleCode: sessionData.roleCode,
119
117
  roleType: sessionData.roleType
120
118
  }
@@ -0,0 +1,105 @@
1
+ export default {
2
+ name: "获取错误报告列表",
3
+ method: "POST",
4
+ body: "none",
5
+ auth: true,
6
+ fields: {
7
+ page: { name: "页码", input: "integer", min: 1, max: 9999 },
8
+ limit: { name: "每页数量", input: "integer", min: 1, max: 100 },
9
+ keyword: { name: "关键词", input: "string", min: 0, max: 100 },
10
+ errorType: { name: "错误类型", input: "string", min: 0, max: 50 },
11
+ source: { name: "来源", input: "string", min: 0, max: 50 },
12
+ productName: { name: "产品名称", input: "string", min: 0, max: 100 },
13
+ productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
14
+ productVersion: { name: "产品版本", input: "string", min: 0, max: 100 },
15
+ deviceType: { name: "设备类型", input: "string", min: 0, max: 50 },
16
+ browserName: { name: "浏览器", input: "string", min: 0, max: 100 },
17
+ osName: { name: "操作系统", input: "string", min: 0, max: 100 }
18
+ },
19
+ required: [],
20
+ handler: async (befly, ctx) => {
21
+ const tableExistsResult = await befly.mysql.tableExists("beflyErrorReport");
22
+ const page = Number(ctx.body.page || 1);
23
+ const limit = Number(ctx.body.limit || 20);
24
+
25
+ if (tableExistsResult.data !== true) {
26
+ return befly.tool.Yes("获取成功", {
27
+ lists: [],
28
+ total: 0,
29
+ page: page,
30
+ limit: limit
31
+ });
32
+ }
33
+
34
+ const keyword = String(ctx.body.keyword || "").trim();
35
+ const errorType = String(ctx.body.errorType || "").trim();
36
+ const source = String(ctx.body.source || "").trim();
37
+ const productName = String(ctx.body.productName || "").trim();
38
+ const productCode = String(ctx.body.productCode || "").trim();
39
+ const productVersion = String(ctx.body.productVersion || "").trim();
40
+ const deviceType = String(ctx.body.deviceType || "").trim();
41
+ const browserName = String(ctx.body.browserName || "").trim();
42
+ const osName = String(ctx.body.osName || "").trim();
43
+ const where = {};
44
+
45
+ if (keyword) {
46
+ where["$or"] = [{ pagePath$like: keyword }, { pageName$like: keyword }, { message$like: keyword }, { errorType$like: keyword }, { productName$like: keyword }, { productCode$like: keyword }, { productVersion$like: keyword }, { browserName$like: keyword }, { osName$like: keyword }, { deviceType$like: keyword }, { deviceVendor$like: keyword }, { deviceModel$like: keyword }];
47
+ }
48
+
49
+ if (errorType) {
50
+ where.errorType = errorType;
51
+ }
52
+
53
+ if (source) {
54
+ where.source = source;
55
+ }
56
+
57
+ if (productName) {
58
+ where.productName = productName;
59
+ }
60
+
61
+ if (productCode) {
62
+ where.productCode = productCode;
63
+ }
64
+
65
+ if (productVersion) {
66
+ where.productVersion = productVersion;
67
+ }
68
+
69
+ if (deviceType) {
70
+ where.deviceType = deviceType;
71
+ }
72
+
73
+ if (browserName) {
74
+ where.browserName = browserName;
75
+ }
76
+
77
+ if (osName) {
78
+ where.osName = osName;
79
+ }
80
+
81
+ const result = await befly.mysql.getList({
82
+ table: "beflyErrorReport",
83
+ where: where,
84
+ page: page,
85
+ limit: limit,
86
+ orderBy: ["reportTime#DESC"]
87
+ });
88
+
89
+ const lists = [];
90
+ for (const item of result.data?.lists || []) {
91
+ const row = Object.assign({}, item);
92
+ row.reportTime = Number(item.reportTime || 0);
93
+ row.firstReportTime = Number(item.firstReportTime || 0);
94
+ row.bucketTime = Number(item.bucketTime || 0);
95
+ row.bucketDate = Number(item.bucketDate || 0);
96
+ row.hitCount = Number(item.hitCount || 0) || 1;
97
+ lists.push(row);
98
+ }
99
+
100
+ const data = Object.assign({}, result.data);
101
+ data.lists = lists;
102
+
103
+ return befly.tool.Yes("获取成功", data);
104
+ }
105
+ };
@@ -0,0 +1,91 @@
1
+ import { UAParser } from "ua-parser-js";
2
+ import { getDateYmdNumber, getTimeBucketStart } from "../../utils/datetime.js";
3
+
4
+ const VISIT_STATS_BUCKET_MS = 30 * 60 * 1000;
5
+
6
+ function getErrorReportUaData(ctx) {
7
+ let userAgent = "";
8
+
9
+ if (typeof ctx.req?.headers?.get === "function") {
10
+ userAgent = ctx.req.headers.get("user-agent") || "";
11
+ } else if (typeof ctx.headers?.get === "function") {
12
+ userAgent = ctx.headers.get("user-agent") || "";
13
+ }
14
+
15
+ const uaResult = UAParser(userAgent);
16
+
17
+ return {
18
+ userAgent: userAgent,
19
+ browserName: uaResult.browser.name || "",
20
+ browserVersion: uaResult.browser.version || "",
21
+ osName: uaResult.os.name || "",
22
+ osVersion: uaResult.os.version || "",
23
+ deviceType: uaResult.device.type || "desktop",
24
+ deviceVendor: uaResult.device.vendor || "",
25
+ deviceModel: uaResult.device.model || "",
26
+ engineName: uaResult.engine.name || "",
27
+ cpuArchitecture: uaResult.cpu.architecture || ""
28
+ };
29
+ }
30
+
31
+ export default {
32
+ name: "上报错误报告",
33
+ method: "POST",
34
+ body: "none",
35
+ auth: false,
36
+ fields: {
37
+ pagePath: { name: "页面路径", input: "string", min: 0, max: 200 },
38
+ pageName: { name: "页面名称", input: "string", min: 0, max: 100 },
39
+ source: { name: "来源", input: "string", min: 0, max: 50 },
40
+ productName: { name: "产品名称", input: "string", min: 0, max: 100 },
41
+ productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
42
+ productVersion: { name: "产品版本", input: "string", min: 0, max: 100 },
43
+ errorType: { name: "错误类型", input: "string", min: 0, max: 50 },
44
+ message: { name: "错误信息", input: "string", min: 0, max: 500 },
45
+ detail: { name: "错误详情", input: "string", min: 0, max: 5000 }
46
+ },
47
+ required: [],
48
+ handler: async (befly, ctx) => {
49
+ const now = Date.now();
50
+ const bucketTime = getTimeBucketStart(now, VISIT_STATS_BUCKET_MS);
51
+ const bucketDate = getDateYmdNumber(now);
52
+ const uaData = getErrorReportUaData(ctx);
53
+
54
+ await befly.mysql.insData({
55
+ table: "beflyErrorReport",
56
+ data: {
57
+ reportTime: now,
58
+ firstReportTime: now,
59
+ bucketTime: bucketTime,
60
+ bucketDate: bucketDate,
61
+ hitCount: 1,
62
+ source: ctx.body?.source,
63
+ productName: ctx.body?.productName || "",
64
+ productCode: ctx.body?.productCode || "",
65
+ productVersion: ctx.body?.productVersion || "",
66
+ pagePath: ctx.body?.pagePath || "",
67
+ pageName: ctx.body?.pageName || "",
68
+ errorType: ctx.body?.errorType || "",
69
+ message: ctx.body?.message || "",
70
+ detail: ctx.body?.detail || "",
71
+ userAgent: uaData.userAgent,
72
+ browserName: uaData.browserName,
73
+ browserVersion: uaData.browserVersion,
74
+ osName: uaData.osName,
75
+ osVersion: uaData.osVersion,
76
+ deviceType: uaData.deviceType,
77
+ deviceVendor: uaData.deviceVendor,
78
+ deviceModel: uaData.deviceModel,
79
+ engineName: uaData.engineName,
80
+ cpuArchitecture: uaData.cpuArchitecture
81
+ }
82
+ });
83
+
84
+ return befly.tool.Yes("上报成功", {
85
+ reportTime: now,
86
+ bucketTime: bucketTime,
87
+ bucketDate: bucketDate,
88
+ stored: true
89
+ });
90
+ }
91
+ };
@@ -0,0 +1,153 @@
1
+ import { getDateYmdNumber } from "../../utils/datetime.js";
2
+
3
+ const ERROR_STATS_DAY_LIMIT = 30;
4
+
5
+ function toNumber(value) {
6
+ const num = Number(value);
7
+ if (!Number.isFinite(num)) {
8
+ return 0;
9
+ }
10
+
11
+ return num;
12
+ }
13
+
14
+ function getErrorStatsWeekStartDate(timestamp = Date.now()) {
15
+ const date = Reflect.construct(Date, [timestamp]);
16
+ const day = date.getDay();
17
+ const offset = day === 0 ? -6 : 1 - day;
18
+
19
+ return getDateYmdNumber(timestamp + offset * 24 * 60 * 60 * 1000);
20
+ }
21
+
22
+ function getErrorStatsMonthStartDate(timestamp = Date.now()) {
23
+ const date = Reflect.construct(Date, [timestamp]);
24
+
25
+ return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
26
+ }
27
+
28
+ function getErrorStatsRecentDateList(now = Date.now(), limit = ERROR_STATS_DAY_LIMIT) {
29
+ const list = [];
30
+
31
+ for (let i = limit - 1; i >= 0; i--) {
32
+ list.push(getDateYmdNumber(now - i * 24 * 60 * 60 * 1000));
33
+ }
34
+
35
+ return list;
36
+ }
37
+
38
+ async function getErrorStatsSummary(befly, startDate, endDate) {
39
+ const result = await befly.mysql.execute("SELECT SUM(hit_count) as count FROM befly_error_report WHERE state = 1 AND bucket_date BETWEEN ? AND ?", [startDate, endDate]);
40
+ const detail = result.data?.[0] || {};
41
+
42
+ return {
43
+ count: toNumber(detail.count)
44
+ };
45
+ }
46
+
47
+ export default {
48
+ name: "获取错误统计",
49
+ method: "POST",
50
+ body: "none",
51
+ auth: true,
52
+ fields: {},
53
+ required: [],
54
+ handler: async (befly) => {
55
+ const tableExistsResult = await befly.mysql.tableExists("beflyErrorReport");
56
+ const tableReady = tableExistsResult.data === true;
57
+ const now = Date.now();
58
+ const bucketDate = getDateYmdNumber(now);
59
+
60
+ if (!tableReady) {
61
+ return befly.tool.Yes("获取成功", {
62
+ today: {
63
+ bucketDate: bucketDate,
64
+ count: 0
65
+ },
66
+ week: {
67
+ startDate: getErrorStatsWeekStartDate(now),
68
+ endDate: bucketDate,
69
+ count: 0
70
+ },
71
+ month: {
72
+ startDate: getErrorStatsMonthStartDate(now),
73
+ endDate: bucketDate,
74
+ count: 0
75
+ },
76
+ trend: [],
77
+ days: [],
78
+ topTypes: []
79
+ });
80
+ }
81
+
82
+ const weekStartDate = getErrorStatsWeekStartDate(now);
83
+ const monthStartDate = getErrorStatsMonthStartDate(now);
84
+ const recentDateList = getErrorStatsRecentDateList(now);
85
+ const todaySummary = await getErrorStatsSummary(befly, bucketDate, bucketDate);
86
+ const weekSummary = await getErrorStatsSummary(befly, weekStartDate, bucketDate);
87
+ const monthSummary = await getErrorStatsSummary(befly, monthStartDate, bucketDate);
88
+ const trendRes = await befly.mysql.execute("SELECT bucket_time as bucketTime, bucket_date as bucketDate, SUM(hit_count) as count FROM befly_error_report WHERE state = 1 AND bucket_date = ? GROUP BY bucket_time, bucket_date ORDER BY bucket_time ASC", [bucketDate]);
89
+ const daysRes = await befly.mysql.execute("SELECT bucket_date as bucketDate, SUM(hit_count) as count FROM befly_error_report WHERE state = 1 AND bucket_date >= ? GROUP BY bucket_date ORDER BY bucket_date ASC", [recentDateList[0]]);
90
+ const topTypesRes = await befly.mysql.execute("SELECT error_type as errorType, SUM(hit_count) as count FROM befly_error_report WHERE state = 1 AND bucket_date = ? GROUP BY error_type ORDER BY count DESC LIMIT 5", [bucketDate]);
91
+
92
+ const trend = [];
93
+ for (const item of trendRes.data || []) {
94
+ trend.push({
95
+ bucketTime: toNumber(item.bucketTime),
96
+ bucketDate: toNumber(item.bucketDate),
97
+ count: toNumber(item.count)
98
+ });
99
+ }
100
+
101
+ const daysMap = new Map();
102
+
103
+ for (const item of recentDateList) {
104
+ daysMap.set(item, {
105
+ bucketDate: item,
106
+ count: 0
107
+ });
108
+ }
109
+
110
+ for (const item of daysRes.data || []) {
111
+ const itemBucketDate = toNumber(item.bucketDate);
112
+
113
+ if (!daysMap.has(itemBucketDate)) {
114
+ continue;
115
+ }
116
+
117
+ daysMap.set(itemBucketDate, {
118
+ bucketDate: itemBucketDate,
119
+ count: toNumber(item.count)
120
+ });
121
+ }
122
+
123
+ const days = Array.from(daysMap.values());
124
+
125
+ const topTypes = [];
126
+ for (const item of topTypesRes.data || []) {
127
+ topTypes.push({
128
+ errorType: String(item.errorType || "unknown"),
129
+ count: toNumber(item.count)
130
+ });
131
+ }
132
+
133
+ return befly.tool.Yes("获取成功", {
134
+ today: {
135
+ bucketDate: bucketDate,
136
+ count: todaySummary.count
137
+ },
138
+ week: {
139
+ startDate: weekStartDate,
140
+ endDate: bucketDate,
141
+ count: weekSummary.count
142
+ },
143
+ month: {
144
+ startDate: monthStartDate,
145
+ endDate: bucketDate,
146
+ count: monthSummary.count
147
+ },
148
+ trend: trend,
149
+ days: days,
150
+ topTypes: topTypes
151
+ });
152
+ }
153
+ };
@@ -0,0 +1,104 @@
1
+ import { UAParser } from "ua-parser-js";
2
+
3
+ import { isValidPositiveInt } from "../../utils/is.js";
4
+ import { getDateYmdNumber } from "../../utils/datetime.js";
5
+
6
+ function getInfoStatsMember(ctx) {
7
+ if (isValidPositiveInt(ctx.userId)) {
8
+ return `user:${ctx.userId}`;
9
+ }
10
+
11
+ return `ip:${ctx.ip || "unknown"}`;
12
+ }
13
+
14
+ export default {
15
+ name: "上报访问信息统计",
16
+ method: "POST",
17
+ body: "none",
18
+ auth: false,
19
+ fields: {
20
+ pagePath: { name: "页面路径", input: "string", min: 0, max: 200 },
21
+ pageName: { name: "页面名称", input: "string", min: 0, max: 100 },
22
+ source: { name: "来源", input: "string", min: 0, max: 50 },
23
+ productName: { name: "产品名称", input: "string", min: 0, max: 100 },
24
+ productCode: { name: "产品代号", input: "string", min: 0, max: 100 },
25
+ productVersion: { name: "产品版本", input: "string", min: 0, max: 100 },
26
+ detail: { name: "扩展详情", input: "string", min: 0, max: 5000 }
27
+ },
28
+ required: [],
29
+ handler: async (befly, ctx) => {
30
+ const now = Date.now();
31
+ const reportDate = getDateYmdNumber(now);
32
+ const member = getInfoStatsMember(ctx);
33
+ const tableReady = (await befly.mysql.tableExists("beflyInfoReport")).data === true;
34
+ const body = ctx.body || {};
35
+ let userAgent = "";
36
+
37
+ if (!tableReady) {
38
+ return befly.tool.Yes("上报成功", {
39
+ reportTime: now,
40
+ reportDate: reportDate,
41
+ counted: false,
42
+ stored: false
43
+ });
44
+ }
45
+
46
+ if (typeof ctx.req?.headers?.get === "function") {
47
+ userAgent = ctx.req.headers.get("user-agent") || "";
48
+ } else if (typeof ctx.headers?.get === "function") {
49
+ userAgent = ctx.headers.get("user-agent") || "";
50
+ }
51
+
52
+ const uaResult = UAParser(userAgent);
53
+
54
+ const detail = await befly.mysql.getOne({
55
+ table: "beflyInfoReport",
56
+ where: {
57
+ reportDate: reportDate,
58
+ memberKey: member
59
+ }
60
+ });
61
+
62
+ if (detail.data?.id) {
63
+ return befly.tool.Yes("上报成功", {
64
+ reportTime: now,
65
+ reportDate: reportDate,
66
+ counted: false,
67
+ stored: false
68
+ });
69
+ }
70
+
71
+ await befly.mysql.insData({
72
+ table: "beflyInfoReport",
73
+ data: {
74
+ reportTime: now,
75
+ reportDate: reportDate,
76
+ memberKey: member,
77
+ source: body.source || "",
78
+ productName: body.productName || "",
79
+ productCode: body.productCode || "",
80
+ productVersion: body.productVersion || "",
81
+ pagePath: body.pagePath || "",
82
+ pageName: body.pageName || "",
83
+ detail: body.detail || "",
84
+ userAgent: userAgent,
85
+ deviceType: uaResult.device.type || "desktop",
86
+ browserName: uaResult.browser.name || "",
87
+ browserVersion: uaResult.browser.version || "",
88
+ osName: uaResult.os.name || "",
89
+ osVersion: uaResult.os.version || "",
90
+ deviceVendor: uaResult.device.vendor || "",
91
+ deviceModel: uaResult.device.model || "",
92
+ engineName: uaResult.engine.name || "",
93
+ cpuArchitecture: uaResult.cpu.architecture || ""
94
+ }
95
+ });
96
+
97
+ return befly.tool.Yes("上报成功", {
98
+ reportTime: now,
99
+ reportDate: reportDate,
100
+ counted: true,
101
+ stored: true
102
+ });
103
+ }
104
+ };
@@ -0,0 +1,147 @@
1
+ import { addDays, getDateYmdNumber } from "../../utils/datetime.js";
2
+
3
+ const INFO_STATS_FIELDS = [
4
+ { dbField: "source", key: "sources" },
5
+ { dbField: "product_name", key: "productNames" },
6
+ { dbField: "product_code", key: "productCodes" },
7
+ { dbField: "product_version", key: "productVersions" },
8
+ { dbField: "page_path", key: "pagePaths" },
9
+ { dbField: "page_name", key: "pageNames" },
10
+ { dbField: "device_type", key: "deviceTypes" },
11
+ { dbField: "browser_name", key: "browsers" },
12
+ { dbField: "browser_version", key: "browserVersions" },
13
+ { dbField: "os_name", key: "osList" },
14
+ { dbField: "os_version", key: "osVersions" },
15
+ { dbField: "device_vendor", key: "deviceVendors" },
16
+ { dbField: "device_model", key: "deviceModels" },
17
+ { dbField: "engine_name", key: "engines" },
18
+ { dbField: "cpu_architecture", key: "cpuArchitectures" }
19
+ ];
20
+
21
+ function getInfoStatsWeekStartDate(timestamp = Date.now()) {
22
+ const date = Reflect.construct(Date, [timestamp]);
23
+ const day = date.getDay();
24
+ const offset = day === 0 ? -6 : 1 - day;
25
+
26
+ return getDateYmdNumber(addDays(timestamp, offset));
27
+ }
28
+
29
+ function getInfoStatsMonthStartDate(timestamp = Date.now()) {
30
+ const date = Reflect.construct(Date, [timestamp]);
31
+
32
+ return getDateYmdNumber(Reflect.construct(Date, [date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0]).getTime());
33
+ }
34
+
35
+ function formatInfoStatsList(rows = []) {
36
+ const list = [];
37
+
38
+ for (const item of rows || []) {
39
+ const name = String(item.name || "").trim();
40
+ const count = Number(item.count || 0);
41
+
42
+ if (!name || count <= 0) {
43
+ continue;
44
+ }
45
+
46
+ list.push({
47
+ name: name,
48
+ count: count
49
+ });
50
+ }
51
+
52
+ return list;
53
+ }
54
+
55
+ async function getInfoStatsFieldList(befly, startDate, endDate, dbField) {
56
+ const result = await befly.mysql.execute(`SELECT ${dbField} as name, COUNT(*) as count FROM befly_info_report WHERE state = 1 AND report_date BETWEEN ? AND ? AND ${dbField} <> '' GROUP BY ${dbField} ORDER BY count DESC, name ASC LIMIT 10`, [startDate, endDate]);
57
+
58
+ return formatInfoStatsList(result.data || []);
59
+ }
60
+
61
+ async function getInfoStatsRange(befly, startDate, endDate) {
62
+ const data = {};
63
+
64
+ for (const item of INFO_STATS_FIELDS) {
65
+ data[item.key] = await getInfoStatsFieldList(befly, startDate, endDate, item.dbField);
66
+ }
67
+
68
+ return data;
69
+ }
70
+
71
+ export default {
72
+ name: "获取访问信息统计",
73
+ method: "POST",
74
+ body: "none",
75
+ auth: true,
76
+ fields: {},
77
+ required: [],
78
+ handler: async (befly) => {
79
+ const now = Date.now();
80
+ const reportDate = getDateYmdNumber(now);
81
+ const weekStartDate = getInfoStatsWeekStartDate(now);
82
+ const monthStartDate = getInfoStatsMonthStartDate(now);
83
+
84
+ const today = await getInfoStatsRange(befly, reportDate, reportDate);
85
+ const week = await getInfoStatsRange(befly, weekStartDate, reportDate);
86
+ const month = await getInfoStatsRange(befly, monthStartDate, reportDate);
87
+
88
+ return befly.tool.Yes("获取成功", {
89
+ today: {
90
+ reportDate: reportDate,
91
+ sources: today.sources,
92
+ productNames: today.productNames,
93
+ productCodes: today.productCodes,
94
+ productVersions: today.productVersions,
95
+ pagePaths: today.pagePaths,
96
+ pageNames: today.pageNames,
97
+ deviceTypes: today.deviceTypes,
98
+ browsers: today.browsers,
99
+ browserVersions: today.browserVersions,
100
+ osList: today.osList,
101
+ osVersions: today.osVersions,
102
+ deviceVendors: today.deviceVendors,
103
+ deviceModels: today.deviceModels,
104
+ engines: today.engines,
105
+ cpuArchitectures: today.cpuArchitectures
106
+ },
107
+ week: {
108
+ startDate: weekStartDate,
109
+ endDate: reportDate,
110
+ sources: week.sources,
111
+ productNames: week.productNames,
112
+ productCodes: week.productCodes,
113
+ productVersions: week.productVersions,
114
+ pagePaths: week.pagePaths,
115
+ pageNames: week.pageNames,
116
+ deviceTypes: week.deviceTypes,
117
+ browsers: week.browsers,
118
+ browserVersions: week.browserVersions,
119
+ osList: week.osList,
120
+ osVersions: week.osVersions,
121
+ deviceVendors: week.deviceVendors,
122
+ deviceModels: week.deviceModels,
123
+ engines: week.engines,
124
+ cpuArchitectures: week.cpuArchitectures
125
+ },
126
+ month: {
127
+ startDate: monthStartDate,
128
+ endDate: reportDate,
129
+ sources: month.sources,
130
+ productNames: month.productNames,
131
+ productCodes: month.productCodes,
132
+ productVersions: month.productVersions,
133
+ pagePaths: month.pagePaths,
134
+ pageNames: month.pageNames,
135
+ deviceTypes: month.deviceTypes,
136
+ browsers: month.browsers,
137
+ browserVersions: month.browserVersions,
138
+ osList: month.osList,
139
+ osVersions: month.osVersions,
140
+ deviceVendors: month.deviceVendors,
141
+ deviceModels: month.deviceModels,
142
+ engines: month.engines,
143
+ cpuArchitectures: month.cpuArchitectures
144
+ }
145
+ });
146
+ }
147
+ };