@fenwave/agent 1.1.0

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.
@@ -0,0 +1,193 @@
1
+ import os from 'os';
2
+
3
+ // Format container uptime in a human-readable format
4
+ const formatUptime = (ms) => {
5
+ const seconds = Math.floor(ms / 1000);
6
+ const minutes = Math.floor(seconds / 60);
7
+ const hours = Math.floor(minutes / 60);
8
+ const days = Math.floor(hours / 24);
9
+
10
+ if (days > 0) return `${days} day${days > 1 ? 's' : ''}`;
11
+ if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''}`;
12
+ if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''}`;
13
+ return `${seconds} second${seconds !== 1 ? 's' : ''}`;
14
+ };
15
+
16
+ // Format image size to human-readable format
17
+ const formatSize = (size) => {
18
+ const kb = size / 1000;
19
+ if (kb < 1000) return `${Math.round(kb)} KB`;
20
+
21
+ const mb = size / 1_000_000;
22
+ if (mb < 1000) return `${Math.round(mb)} MB`;
23
+
24
+ const gb = size / 1_000_000_000;
25
+ return `${gb.toFixed(2)} GB`;
26
+ };
27
+
28
+ // Format image creation time to relative time
29
+ const formatCreatedTime = (created) => {
30
+ const now = Date.now() / 1000;
31
+ const diff = now - created;
32
+
33
+ const minutes = Math.floor(diff / 60);
34
+ const hours = Math.floor(minutes / 60);
35
+ const days = Math.floor(hours / 24);
36
+ const months = Math.floor(days / 30);
37
+ const years = Math.floor(months / 12);
38
+
39
+ if (years > 0) return `${years} year${years > 1 ? 's' : ''} ago`;
40
+ if (months > 0) return `${months} month${months > 1 ? 's' : ''} ago`;
41
+ if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
42
+ if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
43
+ if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
44
+ return 'Just now';
45
+ };
46
+
47
+ // Parse log line to extract timestamp, level, and message
48
+ function parseLogLine(line, containerId) {
49
+ let rawLine = line.toString();
50
+
51
+ // Docker logs can contain binary headers (8 bytes) that need to be stripped
52
+ // The first byte indicates the stream type (1=stdout, 2=stderr)
53
+ // Bytes 1-3 are padding, bytes 4-7 contain the length
54
+ if (rawLine.length > 8) {
55
+ const firstByte = rawLine.charCodeAt(0);
56
+ // Check if this looks like a Docker log header
57
+ if (firstByte === 1 || firstByte === 2) {
58
+ // Skip the 8-byte header
59
+ rawLine = rawLine.substring(8);
60
+ }
61
+ }
62
+
63
+ let message = rawLine;
64
+ let timestamp = new Date().toISOString();
65
+ let level = 'INFO';
66
+ const service = 'container';
67
+ let isEmptyLine = false;
68
+
69
+ // Check if this is an empty line (only whitespace or completely empty)
70
+ if (rawLine.trim() === '') {
71
+ isEmptyLine = true;
72
+ message = ''; // Keep as empty for empty lines
73
+ } else {
74
+ // Extract Docker timestamp if present (format: 2025-09-12T14:44:10.598127086Z)
75
+ const timestampMatch = message.match(
76
+ /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)\s*(.*)$/
77
+ );
78
+ if (timestampMatch) {
79
+ timestamp = timestampMatch[1];
80
+ message = timestampMatch[2] || ''; // This is the actual log content after the Docker timestamp
81
+
82
+ // If after extracting timestamp, message is empty, mark as empty line
83
+ if (message.trim() === '') {
84
+ isEmptyLine = true;
85
+ message = '';
86
+ }
87
+ }
88
+
89
+ // Clean up any remaining malformed timestamp artifacts
90
+ message = message.replace(
91
+ /^-?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z\s*/,
92
+ ''
93
+ );
94
+
95
+ // Try to detect log level from content (only for non-empty lines)
96
+ if (!isEmptyLine) {
97
+ const lowerMessage = message.toLowerCase();
98
+ if (
99
+ lowerMessage.includes('"s":"e"') ||
100
+ lowerMessage.includes('error') ||
101
+ lowerMessage.includes('err') ||
102
+ lowerMessage.includes('fatal')
103
+ ) {
104
+ level = 'ERROR';
105
+ } else if (
106
+ lowerMessage.includes('"s":"w"') ||
107
+ lowerMessage.includes('warn')
108
+ ) {
109
+ level = 'WARN';
110
+ } else if (
111
+ lowerMessage.includes('"s":"d"') ||
112
+ lowerMessage.includes('debug')
113
+ ) {
114
+ level = 'DEBUG';
115
+ } else if (
116
+ lowerMessage.includes('"s":"i"') ||
117
+ lowerMessage.includes('info') ||
118
+ lowerMessage.includes('log:')
119
+ ) {
120
+ level = 'INFO';
121
+ }
122
+ }
123
+ }
124
+
125
+ return {
126
+ id: `log-${containerId}-${Date.now()}-${Math.random()
127
+ .toString(36)
128
+ .substring(2, 9)}`,
129
+ timestamp,
130
+ level,
131
+ service,
132
+ message,
133
+ isEmptyLine, // Add flag to identify empty lines
134
+ };
135
+ }
136
+
137
+ // Parse size strings like "10 GB" to bytes
138
+ function parseSize(sizeStr) {
139
+ if (!sizeStr || typeof sizeStr !== 'string') return 0;
140
+
141
+ const match = sizeStr.match(/^(\d+(\.\d+)?)\s*([KMGT]?B)$/);
142
+ if (!match) return 0;
143
+
144
+ const size = Number.parseFloat(match[1]);
145
+ const unit = match[3];
146
+
147
+ switch (unit) {
148
+ case 'B':
149
+ return size;
150
+ case 'KB':
151
+ return size * 1024;
152
+ case 'MB':
153
+ return size * 1024 * 1024;
154
+ case 'GB':
155
+ return size * 1024 * 1024 * 1024;
156
+ case 'TB':
157
+ return size * 1024 * 1024 * 1024 * 1024;
158
+ default:
159
+ return size;
160
+ }
161
+ }
162
+
163
+ // Simple CPU usage estimation without native modules
164
+ function estimateCpuUsage() {
165
+ try {
166
+ const cpus = os.cpus();
167
+ let idle = 0;
168
+ let total = 0;
169
+
170
+ for (const cpu of cpus) {
171
+ for (const type in cpu.times) {
172
+ total += cpu.times[type];
173
+ }
174
+ idle += cpu.times.idle;
175
+ }
176
+
177
+ // Calculate CPU usage as percentage of non-idle time
178
+ const usage = 100 - (idle / total) * 100;
179
+ return Math.round(usage * 100) / 100;
180
+ } catch (error) {
181
+ console.error('Error estimating CPU usage:', error.message);
182
+ return 0;
183
+ }
184
+ }
185
+
186
+ export {
187
+ formatUptime,
188
+ formatSize,
189
+ formatCreatedTime,
190
+ parseLogLine,
191
+ parseSize,
192
+ estimateCpuUsage,
193
+ };
package/index.html ADDED
@@ -0,0 +1,60 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Authorization Successful</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @keyframes fadeIn {
10
+ from {
11
+ opacity: 0;
12
+ }
13
+ to {
14
+ opacity: 1;
15
+ }
16
+ }
17
+ .fade-in {
18
+ animation: fadeIn 0.5s ease-in-out;
19
+ }
20
+ </style>
21
+ </head>
22
+ <body class="bg-gray-50 min-h-screen">
23
+ <div class="flex justify-center items-center min-h-[80vh]">
24
+ <div
25
+ class="fade-in max-w-sm mx-auto mt-8 p-8 text-center bg-white rounded-lg shadow-lg"
26
+ >
27
+ <!-- Success Icon Circle -->
28
+ <div
29
+ class="w-16 h-16 mx-auto mb-6 flex items-center justify-center rounded-full bg-green-500 text-white"
30
+ >
31
+ <svg
32
+ class="w-8 h-8"
33
+ fill="currentColor"
34
+ viewBox="0 0 20 20"
35
+ xmlns="http://www.w3.org/2000/svg"
36
+ >
37
+ <path
38
+ fill-rule="evenodd"
39
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
40
+ clip-rule="evenodd"
41
+ ></path>
42
+ </svg>
43
+ </div>
44
+
45
+ <!-- Main Heading -->
46
+ <h2 class="text-2xl font-medium text-gray-900 mb-4">
47
+ Authorization Successful
48
+ </h2>
49
+
50
+ <!-- Description Text -->
51
+ <p class="text-base text-gray-600 mb-8">
52
+ Your agent has been successfully authorized.
53
+ </p>
54
+
55
+ <!-- Footer Text -->
56
+ <p class="text-sm text-gray-500">You may now return to your CLI.</p>
57
+ </div>
58
+ </div>
59
+ </body>
60
+ </html>