@drocketxx/pm2me 1.1.37 → 1.1.38

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.
@@ -1,390 +0,0 @@
1
- <template>
2
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
3
- <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center space-y-4 sm:space-y-0">
4
- <div>
5
- <h1 class="text-3xl font-bold text-white tracking-tight">Applications</h1>
6
- <p class="text-sm text-slate-400 mt-1">Manage and monitor your PM2 deployments</p>
7
- </div>
8
- <button @click="showDeployModal = true"
9
- class="flex items-center px-4 py-2.5 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white text-sm font-medium rounded-xl transition-all shadow-lg shadow-blue-500/30 transform hover:-translate-y-0.5">
10
- <svg class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
11
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
12
- </svg>
13
- Deploy New App
14
- </button>
15
- </div>
16
-
17
- <!-- PM2 Apps List -->
18
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
19
- <div v-for="app in pm2Apps" :key="app.pm_id"
20
- class="bg-slate-800/80 backdrop-blur-sm rounded-2xl shadow-xl border border-slate-700 p-6 flex flex-col transition-all hover:border-slate-600 hover:shadow-2xl">
21
- <div class="flex items-center justify-between mb-4">
22
- <div class="flex items-center space-x-3">
23
- <div class="w-10 h-10 rounded-xl bg-slate-700/50 flex items-center justify-center border border-slate-600">
24
- <svg class="h-6 w-6 text-slate-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
25
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
26
- d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
27
- </svg>
28
- </div>
29
- <div>
30
- <h2 class="text-lg font-bold text-white">{{ app.name }}</h2>
31
- <div class="flex items-center space-x-2 text-xs font-medium">
32
- <span class="relative flex h-2 w-2">
33
- <span v-if="app.pm2_env.status === 'online'"
34
- class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
35
- <span class="relative inline-flex rounded-full h-2 w-2"
36
- :class="getStatusColor(app.pm2_env.status)"></span>
37
- </span>
38
- <span :class="getStatusTextColor(app.pm2_env.status)" class="capitalize">{{ app.pm2_env.status }}</span>
39
- <span v-if="app.commitMessage"
40
- class="ml-2 text-[10px] bg-slate-700/50 border border-slate-600 px-1.5 py-0.5 rounded text-slate-300 font-mono inline-block max-w-[150px] truncate align-bottom"
41
- title="Deployed Commit">
42
- <svg class="w-3 h-3 inline pb-[1px] mr-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
43
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 17l4 4 4-4m-4-5v9" />
44
- </svg>{{ app.commitMessage }}
45
- </span>
46
- </div>
47
- </div>
48
- </div>
49
- <div class="flex items-center space-x-2">
50
- <button @click="openEditModal(app, true)"
51
- class="p-2 text-slate-400 hover:text-blue-400 bg-slate-700/50 hover:bg-slate-700 rounded-lg transition-colors border border-slate-600 focus:outline-none"
52
- title="Edit Config">
53
- <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
54
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
55
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
56
- </svg>
57
- </button>
58
- <button @click="openLogs(app)"
59
- class="p-2 text-slate-400 hover:text-white bg-slate-700/50 hover:bg-slate-700 rounded-lg transition-colors border border-slate-600 focus:outline-none"
60
- title="View Logs">
61
- <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
62
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
63
- d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
64
- </svg>
65
- </button>
66
- </div>
67
- </div>
68
-
69
- <div class="grid grid-cols-2 gap-4 mb-6 text-sm">
70
- <div class="bg-slate-900/50 rounded-lg p-3 border border-slate-700">
71
- <p class="text-slate-500 mb-1">CPU Usage</p>
72
- <p class="font-semibold text-slate-200">{{ app.monit ? app.monit.cpu : 0 }}%</p>
73
- </div>
74
- <div class="bg-slate-900/50 rounded-lg p-3 border border-slate-700">
75
- <p class="text-slate-500 mb-1">Memory</p>
76
- <p class="font-semibold text-slate-200">{{ formatBytes(app.monit ? app.monit.memory : 0) }}</p>
77
- </div>
78
- <div class="bg-slate-900/50 rounded-lg p-3 border border-slate-700">
79
- <p class="text-slate-500 mb-1">Restarts</p>
80
- <p class="font-semibold text-slate-200">{{ app.pm2_env.restart_time }}</p>
81
- </div>
82
- <div class="bg-slate-900/50 rounded-lg p-3 border border-slate-700 flex flex-col justify-center text-center">
83
- <p class="text-slate-400 text-xs">ID: {{ app.pm_id }}</p>
84
- </div>
85
- </div>
86
-
87
- <div class="mt-auto grid grid-cols-3 gap-2">
88
- <button v-if="app.pm2_env.status === 'online'" @click="pm2Action('stop', app.name)"
89
- class="flex items-center justify-center p-2 text-sm font-medium text-amber-500 bg-amber-500/10 hover:bg-amber-500/20 rounded-lg transition-colors border border-amber-500/20">
90
- <svg class="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
91
- <path fill-rule="evenodd"
92
- d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z"
93
- clip-rule="evenodd" />
94
- </svg>Stop
95
- </button>
96
- <button v-else @click="pm2Action('start', app.name)"
97
- class="flex items-center justify-center p-2 text-sm font-medium text-emerald-500 bg-emerald-500/10 hover:bg-emerald-500/20 rounded-lg transition-colors border border-emerald-500/20">
98
- <svg class="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
99
- <path fill-rule="evenodd"
100
- d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
101
- clip-rule="evenodd" />
102
- </svg>Start
103
- </button>
104
-
105
- <button @click="pm2Action('restart', app.name)"
106
- class="flex items-center justify-center p-2 text-sm font-medium text-blue-400 bg-blue-400/10 hover:bg-blue-400/20 rounded-lg transition-colors border border-blue-400/20">
107
- <svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
108
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
109
- d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
110
- </svg>Restart
111
- </button>
112
-
113
- <button @click="retryDeployForPm2App(app)"
114
- class="flex items-center justify-center p-2 text-sm font-medium text-purple-400 bg-purple-400/10 hover:bg-purple-400/20 rounded-lg transition-colors border border-purple-400/20 text-nowrap">
115
- <svg class="w-4 h-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
116
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
117
- d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
118
- </svg>Deploy
119
- </button>
120
- </div>
121
- </div>
122
-
123
- <!-- Database Apps (Deploying/Failed) -->
124
- <div v-for="dbApp in deployingApps" :key="dbApp.id"
125
- class="bg-slate-800/80 backdrop-blur-sm rounded-2xl shadow-xl border border-slate-700/50 p-6 flex flex-col relative overflow-hidden">
126
- <div v-if="dbApp.status === 'deploying'"
127
- class="absolute inset-0 bg-blue-500/5 animate-pulse rounded-2xl blur-xl -z-10"></div>
128
- <div class="flex items-center justify-between mb-4">
129
- <div class="flex items-center space-x-3">
130
- <div class="w-10 h-10 rounded-xl bg-slate-700/50 flex items-center justify-center border border-slate-600">
131
- <svg v-if="dbApp.status === 'deploying'" class="animate-spin h-6 w-6 text-blue-400"
132
- xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
133
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
134
- <path class="opacity-75" fill="currentColor"
135
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
136
- </path>
137
- </svg>
138
- <svg v-else class="h-6 w-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
139
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
140
- d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
141
- </svg>
142
- </div>
143
- <div>
144
- <h2 class="text-lg font-bold text-white">{{ dbApp.name }}</h2>
145
- <div class="flex items-center space-x-2 text-xs font-medium">
146
- <span class="capitalize" :class="dbApp.status === 'deploying' ? 'text-blue-400' : 'text-red-400'">{{
147
- dbApp.status }}</span>
148
- <span v-if="dbApp.commitMessage"
149
- class="ml-2 text-[10px] bg-slate-700/50 border border-slate-600 px-1.5 py-0.5 rounded text-slate-300 font-mono inline-block max-w-[150px] truncate align-bottom"
150
- title="Deployed Commit">
151
- <svg class="w-3 h-3 inline pb-[1px] mr-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
152
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 17l4 4 4-4m-4-5v9" />
153
- </svg>{{ dbApp.commitMessage }}
154
- </span>
155
- </div>
156
- </div>
157
- </div>
158
- <div class="flex items-center space-x-2">
159
- <button @click="openEditModal(dbApp, false)"
160
- class="p-2 text-slate-400 hover:text-blue-400 bg-slate-700/50 hover:bg-slate-700 rounded-lg transition-colors border border-slate-600 focus:outline-none"
161
- title="Edit Config">
162
- <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
163
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
164
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
165
- </svg>
166
- </button>
167
- <button @click="openDbAppLogs(dbApp)"
168
- class="p-2 text-slate-400 hover:text-white bg-slate-700/50 hover:bg-slate-700 rounded-lg transition-colors border border-slate-600 focus:outline-none"
169
- title="View Logs">
170
- <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
171
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
172
- d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
173
- </svg>
174
- </button>
175
- </div>
176
- </div>
177
- <button v-if="dbApp.status !== 'deploying'" @click="retryDeploy(dbApp.id)"
178
- class="flex items-center justify-center p-2 text-sm font-medium text-purple-400 bg-purple-400/10 hover:bg-purple-400/20 rounded-lg transition-colors border border-purple-400/20 col-span-2">
179
- <svg class="w-4 h-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
180
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
181
- d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
182
- </svg>Deploy
183
- </button>
184
- </div>
185
- </div>
186
-
187
- </div>
188
-
189
- <!-- Empty State -->
190
- <div v-if="pm2Apps.length === 0 && deployingApps.length === 0"
191
- class="flex flex-col items-center justify-center p-12 bg-slate-800/30 rounded-3xl border border-slate-700/50 border-dashed">
192
- <div
193
- class="w-20 h-20 rounded-full bg-slate-800 flex items-center justify-center mb-6 shadow-inner ring-1 ring-slate-700/50">
194
- <svg class="h-10 w-10 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
195
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
196
- d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
197
- </svg>
198
- </div>
199
- <h3 class="text-xl font-bold text-white mb-2">No Applications Found</h3>
200
- <p class="text-slate-400 text-center max-w-sm mb-6">You haven't deployed any applications yet or PM2 is not
201
- managing any processes.</p>
202
- <button @click="showDeployModal = true"
203
- class="px-6 py-3 bg-slate-700 hover:bg-slate-600 text-white font-medium rounded-xl transition-all shadow-lg border border-slate-600">
204
- Deploy Your First App
205
- </button>
206
- </div>
207
-
208
- <DeployModal :isOpen="showDeployModal" :appToEdit="currentAppToEdit"
209
- @close="showDeployModal = false; currentAppToEdit = null" @deploy="fetchApps" @delete="handleAppDelete" />
210
- <LogViewer :isOpen="showLogViewer" :appId="currentLogAppId" :appName="currentLogAppName"
211
- @close="showLogViewer = false" />
212
- </template>
213
-
214
- <script setup>
215
- import { ref, onMounted, onUnmounted, computed } from 'vue'
216
- import DeployModal from '../components/DeployModal.vue'
217
- import LogViewer from '../components/LogViewer.vue'
218
-
219
- const pm2Apps = ref([])
220
- const dbApps = ref([])
221
- const showDeployModal = ref(false)
222
- const showLogViewer = ref(false)
223
- const currentLogAppId = ref('')
224
- const currentLogAppName = ref('')
225
- const currentAppToEdit = ref(null)
226
-
227
- let pollInterval
228
-
229
- const deployingApps = computed(() => {
230
- const pm2Names = pm2Apps.value.map(a => a.name)
231
- return dbApps.value.filter(a => !pm2Names.includes(a.name))
232
- })
233
-
234
- const fetchApps = async () => {
235
- try {
236
- const token = localStorage.getItem('pm2me_token')
237
- const headers = { 'Authorization': `Bearer ${token}` }
238
-
239
- const [pm2Res, dbRes] = await Promise.all([
240
- fetch('http://localhost:12345/api/pm2/list', { headers }),
241
- fetch('http://localhost:12345/api/apps', { headers })
242
- ])
243
-
244
- if (pm2Res.ok) pm2Apps.value = await pm2Res.json()
245
- if (dbRes.ok) dbApps.value = await dbRes.json()
246
-
247
- // Inject commit hash into pm2Apps for display
248
- pm2Apps.value = pm2Apps.value.map(pm2App => {
249
- const dbMatch = dbApps.value.find(db => db.name === pm2App.name)
250
- if (dbMatch) {
251
- pm2App.commitHash = dbMatch.commitHash;
252
- pm2App.commitMessage = dbMatch.commitMessage;
253
- }
254
- return pm2App
255
- })
256
- } catch (e) {
257
- console.error('Fetch apps error:', e)
258
- }
259
- }
260
-
261
- const pm2Action = async (action, name) => {
262
- try {
263
- await fetch(`http://localhost:12345/api/pm2/${action}`, {
264
- method: 'POST',
265
- headers: {
266
- 'Content-Type': 'application/json',
267
- 'Authorization': `Bearer ${localStorage.getItem('pm2me_token')}`
268
- },
269
- body: JSON.stringify({ nameOrId: name })
270
- })
271
- fetchApps()
272
- } catch (e) {
273
- alert(`Failed to ${action} app`)
274
- }
275
- }
276
-
277
- const removeDbApp = async (id) => {
278
- try {
279
- await fetch(`http://localhost:12345/api/apps/${id}`, {
280
- method: 'DELETE',
281
- headers: { 'Authorization': `Bearer ${localStorage.getItem('pm2me_token')}` }
282
- })
283
- fetchApps()
284
- } catch (e) {
285
- console.error('Failed to remove db app', e);
286
- }
287
- }
288
-
289
- const handleAppDelete = async (appToDelete) => {
290
- if (!appToDelete) return;
291
- // Try to gracefully stop and delete from pm2 first if it exists
292
- try {
293
- await fetch(`http://localhost:12345/api/pm2/delete`, {
294
- method: 'POST',
295
- headers: {
296
- 'Content-Type': 'application/json',
297
- 'Authorization': `Bearer ${localStorage.getItem('pm2me_token')}`
298
- },
299
- body: JSON.stringify({ nameOrId: appToDelete.name })
300
- });
301
- } catch (e) {
302
- console.error("Failed to delete PM2 process, proceeding to DB removal")
303
- }
304
- await removeDbApp(appToDelete.id);
305
- }
306
-
307
- const retryDeploy = async (id) => {
308
- try {
309
- fetch(`http://localhost:12345/api/deploy/${id}`, {
310
- method: 'POST',
311
- headers: { 'Authorization': `Bearer ${localStorage.getItem('pm2me_token')}` }
312
- })
313
- // fetchApps immediately to show the "deploying" status
314
- fetchApps()
315
- } catch (e) {
316
- alert('Failed to trigger deployment restart.')
317
- }
318
- }
319
-
320
- const retryDeployForPm2App = (app) => {
321
- const dbApp = dbApps.value.find(db => db.name === app.name);
322
- if (!dbApp) {
323
- alert('Cannot deploy this app as it was not deployed via PM2Me (No config found).');
324
- return;
325
- }
326
- retryDeploy(dbApp.id);
327
- }
328
-
329
- const openLogs = (app) => {
330
- currentLogAppName.value = app.name
331
- // If it's a pm2 app, it doesn't have the db appId attached easily.
332
- // So we'll find matching dbApp by name. If not, use PM2 name as ID (backend needs to support this or stream logic depends on appId)
333
- // For now, let's just make sure PM2 apps match their DB counterpart.
334
- const dbApp = dbApps.value.find(db => db.name === app.name)
335
- currentLogAppId.value = dbApp ? dbApp.id : app.name
336
- showLogViewer.value = true
337
- }
338
-
339
- const openDbAppLogs = (dbApp) => {
340
- currentLogAppName.value = dbApp.name
341
- currentLogAppId.value = dbApp.id
342
- showLogViewer.value = true
343
- }
344
-
345
- const openEditModal = (appOrDbApp, isPm2 = false) => {
346
- let dbApp = appOrDbApp;
347
- if (isPm2) {
348
- dbApp = dbApps.value.find(db => db.name === appOrDbApp.name);
349
- if (!dbApp) {
350
- alert('Cannot edit this app as it was not deployed via PM2Me (No config found).');
351
- return;
352
- }
353
- }
354
- currentAppToEdit.value = dbApp;
355
- showDeployModal.value = true;
356
- }
357
-
358
- // Helpers
359
- const formatBytes = (bytes) => {
360
- if (!bytes) return '0 B'
361
- const k = 1024
362
- const sizes = ['B', 'KB', 'MB', 'GB']
363
- const i = Math.floor(Math.log(bytes) / Math.log(k))
364
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
365
- }
366
-
367
- const getStatusColor = (status) => {
368
- if (status === 'online') return 'bg-emerald-500'
369
- if (status === 'stopping' || status === 'stopped') return 'bg-amber-500'
370
- if (status === 'errored') return 'bg-red-500'
371
- return 'bg-slate-500'
372
- }
373
-
374
- const getStatusTextColor = (status) => {
375
- if (status === 'online') return 'text-emerald-400'
376
- if (status === 'stopping' || status === 'stopped') return 'text-amber-400'
377
- if (status === 'errored') return 'text-red-400'
378
- return 'text-slate-400'
379
- }
380
-
381
- onMounted(() => {
382
- fetchApps()
383
- pollInterval = setInterval(fetchApps, 12345)
384
- })
385
-
386
- onUnmounted(() => {
387
- clearInterval(pollInterval)
388
- })
389
-
390
- </script>