@thibautrey/chatons-extension-minecraft-servers 1.0.0 → 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.
- package/chaton.extension.json +1 -1
- package/index.html +185 -1
- package/index.js +210 -38
- package/package.json +1 -1
package/chaton.extension.json
CHANGED
package/index.html
CHANGED
|
@@ -48,6 +48,14 @@
|
|
|
48
48
|
background: color-mix(in srgb, var(--ce-muted, #e5e7eb) 60%, transparent);
|
|
49
49
|
font-size: 12px;
|
|
50
50
|
}
|
|
51
|
+
.section-header {
|
|
52
|
+
display: flex;
|
|
53
|
+
justify-content: space-between;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 12px;
|
|
56
|
+
margin-bottom: 12px;
|
|
57
|
+
flex-wrap: wrap;
|
|
58
|
+
}
|
|
51
59
|
.field-grid {
|
|
52
60
|
display: grid;
|
|
53
61
|
grid-template-columns: 1fr 1fr;
|
|
@@ -56,11 +64,14 @@
|
|
|
56
64
|
.field-grid .full {
|
|
57
65
|
grid-column: 1 / -1;
|
|
58
66
|
}
|
|
59
|
-
.actions {
|
|
67
|
+
.actions, .form-actions {
|
|
60
68
|
display: flex;
|
|
61
69
|
flex-wrap: wrap;
|
|
62
70
|
gap: 10px;
|
|
63
71
|
}
|
|
72
|
+
.form-actions {
|
|
73
|
+
margin-top: 16px;
|
|
74
|
+
}
|
|
64
75
|
input, textarea, select {
|
|
65
76
|
width: 100%;
|
|
66
77
|
box-sizing: border-box;
|
|
@@ -80,6 +91,10 @@
|
|
|
80
91
|
background: color-mix(in srgb, var(--ce-card, #ffffff) 88%, transparent);
|
|
81
92
|
display: grid;
|
|
82
93
|
gap: 10px;
|
|
94
|
+
transition: all 0.2s ease;
|
|
95
|
+
}
|
|
96
|
+
.server-card:hover {
|
|
97
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
83
98
|
}
|
|
84
99
|
.server-card.is-selected {
|
|
85
100
|
border-color: color-mix(in srgb, #22c55e 55%, var(--ce-border, #d1d5db));
|
|
@@ -116,12 +131,20 @@
|
|
|
116
131
|
}
|
|
117
132
|
.badge.ok {
|
|
118
133
|
background: rgba(34, 197, 94, 0.18);
|
|
134
|
+
color: #15803d;
|
|
119
135
|
}
|
|
120
136
|
.badge.warn {
|
|
121
137
|
background: rgba(245, 158, 11, 0.18);
|
|
138
|
+
color: #a16207;
|
|
122
139
|
}
|
|
123
140
|
.badge.error {
|
|
124
141
|
background: rgba(239, 68, 68, 0.18);
|
|
142
|
+
color: #dc2626;
|
|
143
|
+
}
|
|
144
|
+
.editing-badge {
|
|
145
|
+
background: rgba(59, 130, 246, 0.18);
|
|
146
|
+
color: #2563eb;
|
|
147
|
+
font-weight: 500;
|
|
125
148
|
}
|
|
126
149
|
.row-actions {
|
|
127
150
|
display: flex;
|
|
@@ -137,6 +160,7 @@
|
|
|
137
160
|
border-radius: 12px;
|
|
138
161
|
background: color-mix(in srgb, var(--ce-muted, #e5e7eb) 55%, transparent);
|
|
139
162
|
font-size: 13px;
|
|
163
|
+
text-align: center;
|
|
140
164
|
}
|
|
141
165
|
.status-box {
|
|
142
166
|
display: grid;
|
|
@@ -154,11 +178,171 @@
|
|
|
154
178
|
white-space: pre-wrap;
|
|
155
179
|
word-break: break-word;
|
|
156
180
|
}
|
|
181
|
+
.status-message {
|
|
182
|
+
padding: 10px 14px;
|
|
183
|
+
border-radius: 8px;
|
|
184
|
+
font-size: 13px;
|
|
185
|
+
display: none;
|
|
186
|
+
}
|
|
187
|
+
.status-message.visible {
|
|
188
|
+
display: block;
|
|
189
|
+
}
|
|
190
|
+
.status-message[data-kind="success"] {
|
|
191
|
+
background: rgba(34, 197, 94, 0.15);
|
|
192
|
+
color: #15803d;
|
|
193
|
+
}
|
|
194
|
+
.status-message[data-kind="error"] {
|
|
195
|
+
background: rgba(239, 68, 68, 0.15);
|
|
196
|
+
color: #dc2626;
|
|
197
|
+
}
|
|
198
|
+
.status-message[data-kind="loading"] {
|
|
199
|
+
background: rgba(59, 130, 246, 0.15);
|
|
200
|
+
color: #2563eb;
|
|
201
|
+
}
|
|
202
|
+
.help-text {
|
|
203
|
+
margin-top: 12px;
|
|
204
|
+
font-size: 12px;
|
|
205
|
+
}
|
|
206
|
+
.required {
|
|
207
|
+
color: #dc2626;
|
|
208
|
+
}
|
|
209
|
+
.field-error {
|
|
210
|
+
border-color: #dc2626 !important;
|
|
211
|
+
}
|
|
212
|
+
.field-error-msg {
|
|
213
|
+
color: #dc2626;
|
|
214
|
+
font-size: 11px;
|
|
215
|
+
margin-top: 4px;
|
|
216
|
+
}
|
|
217
|
+
.server-controls {
|
|
218
|
+
display: flex;
|
|
219
|
+
gap: 8px;
|
|
220
|
+
align-items: center;
|
|
221
|
+
flex-wrap: wrap;
|
|
222
|
+
}
|
|
223
|
+
.search-box {
|
|
224
|
+
position: relative;
|
|
225
|
+
flex: 1;
|
|
226
|
+
min-width: 150px;
|
|
227
|
+
}
|
|
228
|
+
.search-box input {
|
|
229
|
+
padding-right: 28px;
|
|
230
|
+
}
|
|
231
|
+
.search-clear {
|
|
232
|
+
position: absolute;
|
|
233
|
+
right: 8px;
|
|
234
|
+
top: 50%;
|
|
235
|
+
transform: translateY(-50%);
|
|
236
|
+
background: none;
|
|
237
|
+
border: none;
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
font-size: 16px;
|
|
240
|
+
color: var(--ce-muted-fg, #6b7280);
|
|
241
|
+
padding: 4px;
|
|
242
|
+
}
|
|
243
|
+
.sort-select {
|
|
244
|
+
width: auto;
|
|
245
|
+
min-width: 120px;
|
|
246
|
+
}
|
|
247
|
+
.sort-direction {
|
|
248
|
+
padding: 6px 10px;
|
|
249
|
+
background: color-mix(in srgb, var(--ce-muted, #e5e7eb) 75%, transparent);
|
|
250
|
+
border: 1px solid color-mix(in srgb, var(--ce-border, #d1d5db) 90%, transparent);
|
|
251
|
+
border-radius: 6px;
|
|
252
|
+
cursor: pointer;
|
|
253
|
+
font-size: 14px;
|
|
254
|
+
}
|
|
255
|
+
.sort-direction:hover {
|
|
256
|
+
background: color-mix(in srgb, var(--ce-muted, #e5e7eb) 90%, transparent);
|
|
257
|
+
}
|
|
258
|
+
.search-results-count {
|
|
259
|
+
margin-top: 8px;
|
|
260
|
+
font-size: 12px;
|
|
261
|
+
}
|
|
262
|
+
.btn-icon {
|
|
263
|
+
margin-right: 4px;
|
|
264
|
+
}
|
|
265
|
+
/* Modal styles */
|
|
266
|
+
.modal-overlay {
|
|
267
|
+
position: fixed;
|
|
268
|
+
top: 0;
|
|
269
|
+
left: 0;
|
|
270
|
+
right: 0;
|
|
271
|
+
bottom: 0;
|
|
272
|
+
background: rgba(0, 0, 0, 0.5);
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
justify-content: center;
|
|
276
|
+
z-index: 1000;
|
|
277
|
+
padding: 20px;
|
|
278
|
+
}
|
|
279
|
+
.modal-content {
|
|
280
|
+
background: color-mix(in srgb, var(--ce-card, #ffffff) 98%, transparent);
|
|
281
|
+
border-radius: 16px;
|
|
282
|
+
max-width: 400px;
|
|
283
|
+
width: 100%;
|
|
284
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
285
|
+
overflow: hidden;
|
|
286
|
+
}
|
|
287
|
+
.modal-header {
|
|
288
|
+
display: flex;
|
|
289
|
+
justify-content: space-between;
|
|
290
|
+
align-items: center;
|
|
291
|
+
padding: 16px 20px;
|
|
292
|
+
border-bottom: 1px solid color-mix(in srgb, var(--ce-border, #d1d5db) 90%, transparent);
|
|
293
|
+
}
|
|
294
|
+
.modal-header h3 {
|
|
295
|
+
margin: 0;
|
|
296
|
+
font-size: 16px;
|
|
297
|
+
}
|
|
298
|
+
.modal-close {
|
|
299
|
+
background: none;
|
|
300
|
+
border: none;
|
|
301
|
+
font-size: 24px;
|
|
302
|
+
cursor: pointer;
|
|
303
|
+
color: var(--ce-muted-fg, #6b7280);
|
|
304
|
+
padding: 0;
|
|
305
|
+
line-height: 1;
|
|
306
|
+
}
|
|
307
|
+
.modal-close:hover {
|
|
308
|
+
color: inherit;
|
|
309
|
+
}
|
|
310
|
+
.modal-body {
|
|
311
|
+
padding: 20px;
|
|
312
|
+
}
|
|
313
|
+
.modal-body p {
|
|
314
|
+
margin: 0 0 8px 0;
|
|
315
|
+
}
|
|
316
|
+
.modal-footer {
|
|
317
|
+
display: flex;
|
|
318
|
+
justify-content: flex-end;
|
|
319
|
+
gap: 10px;
|
|
320
|
+
padding: 16px 20px;
|
|
321
|
+
border-top: 1px solid color-mix(in srgb, var(--ce-border, #d1d5db) 90%, transparent);
|
|
322
|
+
}
|
|
323
|
+
/* Danger button style */
|
|
324
|
+
.chaton-ui-button--danger {
|
|
325
|
+
background: rgba(239, 68, 68, 0.15);
|
|
326
|
+
color: #dc2626;
|
|
327
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
328
|
+
}
|
|
329
|
+
.chaton-ui-button--danger:hover {
|
|
330
|
+
background: rgba(239, 68, 68, 0.25);
|
|
331
|
+
}
|
|
332
|
+
/* Responsive */
|
|
157
333
|
@media (max-width: 960px) {
|
|
158
334
|
.layout, .field-grid {
|
|
159
335
|
grid-template-columns: 1fr;
|
|
160
336
|
}
|
|
161
337
|
}
|
|
338
|
+
@media (max-width: 600px) {
|
|
339
|
+
.server-controls {
|
|
340
|
+
width: 100%;
|
|
341
|
+
}
|
|
342
|
+
.search-box {
|
|
343
|
+
width: 100%;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
162
346
|
</style>
|
|
163
347
|
</head>
|
|
164
348
|
<body>
|
package/index.js
CHANGED
|
@@ -11,6 +11,12 @@ const DEFAULT_FORM = {
|
|
|
11
11
|
tags: '',
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const SORT_OPTIONS = {
|
|
15
|
+
name: 'Nom',
|
|
16
|
+
status: 'Statut',
|
|
17
|
+
created: 'Date creation',
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
function clone(value) {
|
|
15
21
|
return JSON.parse(JSON.stringify(value))
|
|
16
22
|
}
|
|
@@ -42,6 +48,10 @@ const state = {
|
|
|
42
48
|
message: '',
|
|
43
49
|
messageKind: '',
|
|
44
50
|
loading: false,
|
|
51
|
+
searchQuery: '',
|
|
52
|
+
sortBy: 'name',
|
|
53
|
+
sortAsc: true,
|
|
54
|
+
pendingDeleteId: null,
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
function normalizeTagsText(tags) {
|
|
@@ -103,6 +113,47 @@ async function loadServers() {
|
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
115
|
|
|
116
|
+
function getFilteredServers() {
|
|
117
|
+
let servers = [...state.servers]
|
|
118
|
+
|
|
119
|
+
// Filter by search query
|
|
120
|
+
if (state.searchQuery) {
|
|
121
|
+
const query = state.searchQuery.toLowerCase()
|
|
122
|
+
servers = servers.filter(server => {
|
|
123
|
+
const name = (server.name || '').toLowerCase()
|
|
124
|
+
const host = (server.host || '').toLowerCase()
|
|
125
|
+
const tags = (server.tags || []).join(' ').toLowerCase()
|
|
126
|
+
return name.includes(query) || host.includes(query) || tags.includes(query)
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Sort servers
|
|
131
|
+
servers.sort((a, b) => {
|
|
132
|
+
let aVal, bVal
|
|
133
|
+
switch (state.sortBy) {
|
|
134
|
+
case 'status':
|
|
135
|
+
const aStatus = state.liveById[a.id]
|
|
136
|
+
const bStatus = state.liveById[b.id]
|
|
137
|
+
aVal = aStatus && aStatus.ok ? 1 : 0
|
|
138
|
+
bVal = bStatus && bStatus.ok ? 1 : 0
|
|
139
|
+
break
|
|
140
|
+
case 'created':
|
|
141
|
+
aVal = a.createdAt || ''
|
|
142
|
+
bVal = b.createdAt || ''
|
|
143
|
+
break
|
|
144
|
+
case 'name':
|
|
145
|
+
default:
|
|
146
|
+
aVal = (a.name || '').toLowerCase()
|
|
147
|
+
bVal = (b.name || '').toLowerCase()
|
|
148
|
+
}
|
|
149
|
+
if (aVal < bVal) return state.sortAsc ? -1 : 1
|
|
150
|
+
if (aVal > bVal) return state.sortAsc ? 1 : -1
|
|
151
|
+
return 0
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return servers
|
|
155
|
+
}
|
|
156
|
+
|
|
106
157
|
function describeLiveStatus(status) {
|
|
107
158
|
if (!status) return '<span class="badge">Aucun statut charge</span>'
|
|
108
159
|
if (status.ok === false) {
|
|
@@ -125,8 +176,9 @@ function describeLiveStatus(status) {
|
|
|
125
176
|
function serverCardHtml(server) {
|
|
126
177
|
const status = state.liveById[server.id]
|
|
127
178
|
const endpoint = server.host + ':' + server.port
|
|
179
|
+
const isEditing = state.selectedId === server.id
|
|
128
180
|
return `
|
|
129
|
-
<div class="server-card ${
|
|
181
|
+
<div class="server-card ${isEditing ? 'is-selected' : ''}" data-server-id="${escapeHtml(server.id)}">
|
|
130
182
|
<div class="server-head">
|
|
131
183
|
<div class="server-title">
|
|
132
184
|
<h3>${escapeHtml(server.name)}</h3>
|
|
@@ -134,16 +186,19 @@ function serverCardHtml(server) {
|
|
|
134
186
|
<small class="muted">${escapeHtml(server.notes || '')}</small>
|
|
135
187
|
</div>
|
|
136
188
|
<div class="server-badges">
|
|
189
|
+
${isEditing ? '<span class="badge editing-badge">Edition</span>' : ''}
|
|
137
190
|
<span class="badge">${escapeHtml(server.edition || 'auto')}</span>
|
|
138
191
|
${(server.tags || []).map((tag) => `<span class="badge">${escapeHtml(tag)}</span>`).join('')}
|
|
139
192
|
</div>
|
|
140
193
|
</div>
|
|
141
194
|
<div class="server-badges">${describeLiveStatus(status)}</div>
|
|
142
195
|
<div class="row-actions">
|
|
143
|
-
<button class="chaton-ui-button chaton-ui-button--ghost" data-action="select" data-server-id="${escapeHtml(server.id)}">
|
|
196
|
+
<button class="chaton-ui-button ${isEditing ? 'chaton-ui-button--primary' : 'chaton-ui-button--ghost'}" data-action="select" data-server-id="${escapeHtml(server.id)}">
|
|
197
|
+
${isEditing ? 'En cours d\'edition' : 'Editer'}
|
|
198
|
+
</button>
|
|
144
199
|
<button class="chaton-ui-button" data-action="probe" data-server-id="${escapeHtml(server.id)}">Tester</button>
|
|
145
|
-
<button class="chaton-ui-button chaton-ui-button--ghost" data-action="
|
|
146
|
-
<button class="chaton-ui-button chaton-ui-button--
|
|
200
|
+
<button class="chaton-ui-button chaton-ui-button--ghost" data-action="copy" data-server-id="${escapeHtml(server.id)}" title="Copier l'adresse">Copier</button>
|
|
201
|
+
<button class="chaton-ui-button chaton-ui-button--danger" data-action="confirm-delete" data-server-id="${escapeHtml(server.id)}">Supprimer</button>
|
|
147
202
|
</div>
|
|
148
203
|
</div>
|
|
149
204
|
`
|
|
@@ -154,7 +209,7 @@ function statusDetailHtml(server, status) {
|
|
|
154
209
|
return '<div class="empty">Selectionne un serveur pour voir les details.</div>'
|
|
155
210
|
}
|
|
156
211
|
if (!status) {
|
|
157
|
-
return '<div class="empty">Aucun statut live charge pour ce serveur.</div>'
|
|
212
|
+
return '<div class="empty">Aucun statut live charge pour ce serveur. Clique sur "Tester" pour obtenir le statut.</div>'
|
|
158
213
|
}
|
|
159
214
|
return `
|
|
160
215
|
<div class="status-box">
|
|
@@ -164,11 +219,37 @@ function statusDetailHtml(server, status) {
|
|
|
164
219
|
`
|
|
165
220
|
}
|
|
166
221
|
|
|
222
|
+
function modalHtml() {
|
|
223
|
+
if (!state.pendingDeleteId) return ''
|
|
224
|
+
const server = state.servers.find(s => s.id === state.pendingDeleteId)
|
|
225
|
+
if (!server) return ''
|
|
226
|
+
return `
|
|
227
|
+
<div class="modal-overlay" id="deleteModal">
|
|
228
|
+
<div class="modal-content">
|
|
229
|
+
<div class="modal-header">
|
|
230
|
+
<h3>Confirmer la suppression</h3>
|
|
231
|
+
<button class="modal-close" data-action="cancel-delete">×</button>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="modal-body">
|
|
234
|
+
<p>Es-tu sur de vouloir supprimer le serveur <strong>${escapeHtml(server.name)}</strong> ?</p>
|
|
235
|
+
<p class="muted">Cette action est irreversible.</p>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="modal-footer">
|
|
238
|
+
<button class="chaton-ui-button chaton-ui-button--ghost" data-action="cancel-delete">Annuler</button>
|
|
239
|
+
<button class="chaton-ui-button chaton-ui-button--danger" data-action="confirm-delete-final" data-server-id="${escapeHtml(server.id)}">Supprimer definitivement</button>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
`
|
|
244
|
+
}
|
|
245
|
+
|
|
167
246
|
function render() {
|
|
168
247
|
const app = document.getElementById('app')
|
|
169
248
|
const selected = selectedServer()
|
|
249
|
+
const filteredServers = getFilteredServers()
|
|
170
250
|
const statsCount = state.servers.length
|
|
171
251
|
const onlineCount = Object.values(state.liveById).filter((entry) => entry && entry.ok !== false).length
|
|
252
|
+
const isEditing = !!state.form.id
|
|
172
253
|
|
|
173
254
|
app.innerHTML = `
|
|
174
255
|
<div class="toolbar">
|
|
@@ -178,24 +259,31 @@ function render() {
|
|
|
178
259
|
</div>
|
|
179
260
|
<div class="actions">
|
|
180
261
|
<button id="refreshAllBtn" class="chaton-ui-button">Actualiser tout</button>
|
|
181
|
-
<button id="resetFormBtn" class="chaton-ui-button chaton-ui-button--ghost">
|
|
262
|
+
<button id="resetFormBtn" class="chaton-ui-button ${isEditing ? 'chaton-ui-button--primary' : 'chaton-ui-button--ghost'}">
|
|
263
|
+
${isEditing ? 'Nouveau serveur' : '+ Ajouter'}
|
|
264
|
+
</button>
|
|
182
265
|
</div>
|
|
183
266
|
</div>
|
|
184
267
|
|
|
185
|
-
<div id="statusMessage" class="
|
|
268
|
+
<div id="statusMessage" class="status-message ${state.messageKind ? 'visible' : ''}" data-kind="${escapeHtml(state.messageKind)}">${escapeHtml(state.message)}</div>
|
|
186
269
|
|
|
187
270
|
<div class="layout">
|
|
188
271
|
<section class="ce-card">
|
|
189
272
|
<div class="ce-card__body">
|
|
190
|
-
<
|
|
273
|
+
<div class="section-header">
|
|
274
|
+
<h2 class="ce-section-title">${isEditing ? 'Modifier le serveur' : 'Ajouter un serveur'}</h2>
|
|
275
|
+
${isEditing ? '<span class="badge editing-badge">Mode edition</span>' : ''}
|
|
276
|
+
</div>
|
|
191
277
|
<div class="field-grid">
|
|
192
278
|
<div class="full ce-field">
|
|
193
|
-
<label class="ce-label" for="field-name">Nom
|
|
194
|
-
<input id="field-name" value="${escapeHtml(state.form.name)}" placeholder="Mon serveur survie">
|
|
279
|
+
<label class="ce-label" for="field-name">Nom <span class="required">*</span></label>
|
|
280
|
+
<input id="field-name" value="${escapeHtml(state.form.name)}" placeholder="Mon serveur survie" class="${!state.form.name && state.form.host ? 'field-error' : ''}" required>
|
|
281
|
+
${!state.form.name && state.form.host ? '<small class="field-error-msg">Le nom est requis</small>' : ''}
|
|
195
282
|
</div>
|
|
196
283
|
<div class="ce-field">
|
|
197
|
-
<label class="ce-label" for="field-host">Host
|
|
198
|
-
<input id="field-host" value="${escapeHtml(state.form.host)}" placeholder="play.example.net">
|
|
284
|
+
<label class="ce-label" for="field-host">Host <span class="required">*</span></label>
|
|
285
|
+
<input id="field-host" value="${escapeHtml(state.form.host)}" placeholder="play.example.net" class="${!state.form.host && state.form.name ? 'field-error' : ''}" required>
|
|
286
|
+
${!state.form.host && state.form.name ? '<small class="field-error-msg">Le host est requis</small>' : ''}
|
|
199
287
|
</div>
|
|
200
288
|
<div class="ce-field">
|
|
201
289
|
<label class="ce-label" for="field-port">Port</label>
|
|
@@ -219,21 +307,41 @@ function render() {
|
|
|
219
307
|
</div>
|
|
220
308
|
</div>
|
|
221
309
|
<input id="field-id" type="hidden" value="${escapeHtml(state.form.id)}">
|
|
222
|
-
<div class="actions">
|
|
223
|
-
<button id="saveBtn" class="chaton-ui-button chaton-ui-button--primary"
|
|
224
|
-
|
|
310
|
+
<div class="form-actions">
|
|
311
|
+
<button id="saveBtn" class="chaton-ui-button chaton-ui-button--primary">
|
|
312
|
+
<span class="btn-icon">${isEditing ? '✓' : '+'}</span>
|
|
313
|
+
${isEditing ? 'Mettre a jour' : 'Ajouter le serveur'}
|
|
314
|
+
</button>
|
|
315
|
+
<button id="probeBtn" class="chaton-ui-button" ${!state.form.host ? 'disabled' : ''}>Tester</button>
|
|
225
316
|
<button id="playersBtn" class="chaton-ui-button chaton-ui-button--ghost">Lister les joueurs</button>
|
|
226
317
|
</div>
|
|
227
|
-
<div class="muted">Les informations live utilisent uniquement le ping public du serveur. La liste des joueurs depend de ce que le serveur expose.</div>
|
|
318
|
+
<div class="muted help-text">Les informations live utilisent uniquement le ping public du serveur. La liste des joueurs depend de ce que le serveur expose.</div>
|
|
228
319
|
</div>
|
|
229
320
|
</section>
|
|
230
321
|
|
|
231
322
|
<section class="ce-card">
|
|
232
323
|
<div class="ce-card__body">
|
|
233
|
-
<
|
|
324
|
+
<div class="section-header">
|
|
325
|
+
<h2 class="ce-section-title">Serveurs enregistres</h2>
|
|
326
|
+
<div class="server-controls">
|
|
327
|
+
<div class="search-box">
|
|
328
|
+
<input type="text" id="searchInput" placeholder="Rechercher..." value="${escapeHtml(state.searchQuery)}">
|
|
329
|
+
${state.searchQuery ? '<button class="search-clear" data-action="clear-search">×</button>' : ''}
|
|
330
|
+
</div>
|
|
331
|
+
<select id="sortSelect" class="sort-select">
|
|
332
|
+
<option value="name" ${state.sortBy === 'name' ? 'selected' : ''}>Trier par nom</option>
|
|
333
|
+
<option value="status" ${state.sortBy === 'status' ? 'selected' : ''}>Trier par statut</option>
|
|
334
|
+
<option value="created" ${state.sortBy === 'created' ? 'selected' : ''}>Trier par date</option>
|
|
335
|
+
</select>
|
|
336
|
+
<button class="sort-direction" data-action="toggle-sort" title="Inverser l'ordre">
|
|
337
|
+
${state.sortAsc ? '↑' : '↓'}
|
|
338
|
+
</button>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
234
341
|
<div class="server-list">
|
|
235
|
-
${
|
|
342
|
+
${filteredServers.length ? filteredServers.map(serverCardHtml).join('') : `<div class="empty">${state.searchQuery ? 'Aucun serveur ne correspond a ta recherche.' : 'Aucun serveur configure pour le moment.'}</div>`}
|
|
236
343
|
</div>
|
|
344
|
+
${state.searchQuery && filteredServers.length > 0 ? `<div class="search-results-count muted">${filteredServers.length} serveur(s) trouve(s)</div>` : ''}
|
|
237
345
|
</div>
|
|
238
346
|
</section>
|
|
239
347
|
</div>
|
|
@@ -244,6 +352,8 @@ function render() {
|
|
|
244
352
|
${statusDetailHtml(selected, selected ? state.liveById[selected.id] : null)}
|
|
245
353
|
</div>
|
|
246
354
|
</section>
|
|
355
|
+
|
|
356
|
+
${modalHtml()}
|
|
247
357
|
`
|
|
248
358
|
|
|
249
359
|
bindEvents()
|
|
@@ -272,6 +382,7 @@ async function saveCurrentForm() {
|
|
|
272
382
|
|
|
273
383
|
if (!payload.name || !payload.host) {
|
|
274
384
|
setMessage('Le nom et le host sont obligatoires.', 'error')
|
|
385
|
+
render()
|
|
275
386
|
return
|
|
276
387
|
}
|
|
277
388
|
|
|
@@ -281,6 +392,7 @@ async function saveCurrentForm() {
|
|
|
281
392
|
|
|
282
393
|
if (!result || !result.ok) {
|
|
283
394
|
setMessage(result && result.error ? result.error.message : 'Echec de sauvegarde.', 'error')
|
|
395
|
+
render()
|
|
284
396
|
return
|
|
285
397
|
}
|
|
286
398
|
|
|
@@ -295,10 +407,11 @@ async function probeServer(server) {
|
|
|
295
407
|
setMessage('Aucun serveur selectionne.', 'error')
|
|
296
408
|
return
|
|
297
409
|
}
|
|
410
|
+
setMessage('Test en cours...', 'loading')
|
|
298
411
|
const result = await call('minecraft.servers.status', { id: server.id })
|
|
299
412
|
if (!result || !result.ok) {
|
|
300
413
|
state.liveById[server.id] = { ok: false, error: result && result.error ? result.error.message : 'Probe failed' }
|
|
301
|
-
setMessage(result && result.error ? result.error.message : '
|
|
414
|
+
setMessage(result && result.error ? result.error.message : 'Test impossible.', 'error')
|
|
302
415
|
render()
|
|
303
416
|
return
|
|
304
417
|
}
|
|
@@ -312,9 +425,11 @@ async function listPlayers(server) {
|
|
|
312
425
|
setMessage('Aucun serveur selectionne.', 'error')
|
|
313
426
|
return
|
|
314
427
|
}
|
|
428
|
+
setMessage('Chargement des joueurs...', 'loading')
|
|
315
429
|
const result = await call('minecraft.servers.players', { id: server.id })
|
|
316
430
|
if (!result || !result.ok) {
|
|
317
431
|
setMessage(result && result.error ? result.error.message : 'Impossible de lister les joueurs.', 'error')
|
|
432
|
+
render()
|
|
318
433
|
return
|
|
319
434
|
}
|
|
320
435
|
const players = result.data && Array.isArray(result.data.players) ? result.data.players : []
|
|
@@ -325,10 +440,33 @@ async function listPlayers(server) {
|
|
|
325
440
|
render()
|
|
326
441
|
}
|
|
327
442
|
|
|
443
|
+
async function copyAddress(server) {
|
|
444
|
+
const address = server.host + ':' + server.port
|
|
445
|
+
try {
|
|
446
|
+
await navigator.clipboard.writeText(address)
|
|
447
|
+
setMessage('Adresse copiee: ' + address, 'success')
|
|
448
|
+
} catch (err) {
|
|
449
|
+
setMessage('Impossible de copier l\'adresse.', 'error')
|
|
450
|
+
}
|
|
451
|
+
render()
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function confirmDelete(serverId) {
|
|
455
|
+
state.pendingDeleteId = serverId
|
|
456
|
+
render()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function cancelDelete() {
|
|
460
|
+
state.pendingDeleteId = null
|
|
461
|
+
render()
|
|
462
|
+
}
|
|
463
|
+
|
|
328
464
|
async function removeServer(serverId) {
|
|
465
|
+
state.pendingDeleteId = null
|
|
329
466
|
const result = await call('minecraft.servers.remove', { id: serverId })
|
|
330
467
|
if (!result || !result.ok) {
|
|
331
468
|
setMessage(result && result.error ? result.error.message : 'Suppression impossible.', 'error')
|
|
469
|
+
render()
|
|
332
470
|
return
|
|
333
471
|
}
|
|
334
472
|
delete state.liveById[serverId]
|
|
@@ -341,6 +479,7 @@ async function removeServer(serverId) {
|
|
|
341
479
|
}
|
|
342
480
|
|
|
343
481
|
async function refreshAll() {
|
|
482
|
+
setMessage('Actualisation en cours...', 'loading')
|
|
344
483
|
await loadServers()
|
|
345
484
|
for (const server of state.servers) {
|
|
346
485
|
const result = await call('minecraft.servers.status', { id: server.id })
|
|
@@ -356,6 +495,8 @@ function bindEvents() {
|
|
|
356
495
|
const playersBtn = document.getElementById('playersBtn')
|
|
357
496
|
const resetFormBtn = document.getElementById('resetFormBtn')
|
|
358
497
|
const refreshAllBtn = document.getElementById('refreshAllBtn')
|
|
498
|
+
const searchInput = document.getElementById('searchInput')
|
|
499
|
+
const sortSelect = document.getElementById('sortSelect')
|
|
359
500
|
|
|
360
501
|
if (saveBtn) {
|
|
361
502
|
saveBtn.onclick = function () {
|
|
@@ -384,29 +525,60 @@ function bindEvents() {
|
|
|
384
525
|
void refreshAll()
|
|
385
526
|
}
|
|
386
527
|
}
|
|
528
|
+
if (searchInput) {
|
|
529
|
+
searchInput.oninput = function () {
|
|
530
|
+
state.searchQuery = searchInput.value.trim()
|
|
531
|
+
render()
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (sortSelect) {
|
|
535
|
+
sortSelect.onchange = function () {
|
|
536
|
+
state.sortBy = sortSelect.value
|
|
537
|
+
render()
|
|
538
|
+
}
|
|
539
|
+
}
|
|
387
540
|
|
|
388
|
-
|
|
541
|
+
// Handle all click events via delegation
|
|
542
|
+
document.querySelectorAll('[data-action]').forEach((node) => {
|
|
389
543
|
node.onclick = function () {
|
|
390
544
|
const action = node.getAttribute('data-action')
|
|
391
545
|
const serverId = node.getAttribute('data-server-id')
|
|
392
|
-
const server = state.servers.find((entry) => entry.id === serverId)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
546
|
+
const server = serverId ? state.servers.find((entry) => entry.id === serverId) : null
|
|
547
|
+
|
|
548
|
+
switch (action) {
|
|
549
|
+
case 'select':
|
|
550
|
+
if (server) {
|
|
551
|
+
state.selectedId = server.id
|
|
552
|
+
state.form = formFromServer(server)
|
|
553
|
+
render()
|
|
554
|
+
}
|
|
555
|
+
break
|
|
556
|
+
case 'probe':
|
|
557
|
+
if (server) void probeServer(server)
|
|
558
|
+
break
|
|
559
|
+
case 'players':
|
|
560
|
+
if (server) void listPlayers(server)
|
|
561
|
+
break
|
|
562
|
+
case 'copy':
|
|
563
|
+
if (server) void copyAddress(server)
|
|
564
|
+
break
|
|
565
|
+
case 'confirm-delete':
|
|
566
|
+
if (serverId) confirmDelete(serverId)
|
|
567
|
+
break
|
|
568
|
+
case 'cancel-delete':
|
|
569
|
+
cancelDelete()
|
|
570
|
+
break
|
|
571
|
+
case 'confirm-delete-final':
|
|
572
|
+
if (serverId) void removeServer(serverId)
|
|
573
|
+
break
|
|
574
|
+
case 'toggle-sort':
|
|
575
|
+
state.sortAsc = !state.sortAsc
|
|
576
|
+
render()
|
|
577
|
+
break
|
|
578
|
+
case 'clear-search':
|
|
579
|
+
state.searchQuery = ''
|
|
580
|
+
render()
|
|
581
|
+
break
|
|
410
582
|
}
|
|
411
583
|
}
|
|
412
584
|
})
|