dsh-apk-vm 0.4.1 → 0.6.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/CHANGELOG.md +44 -0
- package/README.md +56 -6
- package/client.js +547 -1
- package/index.js +318 -5
- package/lib/android.js +277 -2
- package/lib/instances.js +6 -0
- package/lib/storage.js +300 -0
- package/package.json +3 -2
package/client.js
CHANGED
|
@@ -189,6 +189,25 @@ window.__ModuleLoader__.load({
|
|
|
189
189
|
'network.caPath': 'CA certificate path (.pem/.crt)',
|
|
190
190
|
'network.installCa': 'Install CA',
|
|
191
191
|
'network.current': 'Current: {value}',
|
|
192
|
+
'network.forwards': 'Port forwards',
|
|
193
|
+
'network.forwardLocal': 'Host port',
|
|
194
|
+
'network.forwardRemote': 'Guest port',
|
|
195
|
+
'network.addForward': 'Add forward',
|
|
196
|
+
'network.remove': 'Remove',
|
|
197
|
+
'network.mode': 'Mode',
|
|
198
|
+
'network.isolated': 'Isolated',
|
|
199
|
+
'network.shared': 'Shared (host bridge)',
|
|
200
|
+
'network.dns': 'DNS servers',
|
|
201
|
+
'network.applyDns': 'Apply DNS',
|
|
202
|
+
'network.hosts': 'Hosts entries',
|
|
203
|
+
'network.applyHosts': 'Apply hosts',
|
|
204
|
+
'network.test': 'Test connectivity',
|
|
205
|
+
'network.testOk': 'Connectivity OK',
|
|
206
|
+
'network.testFail': 'Connectivity check failed',
|
|
207
|
+
'network.pcap': 'Download pcap',
|
|
208
|
+
'network.restartRequired': 'Restart the VM to apply custom DNS',
|
|
209
|
+
'network.sharedHint': 'Shared forwards are reachable from other VMs through the emulator host alias.',
|
|
210
|
+
'network.guestUrl': 'Guest URL: {url}',
|
|
192
211
|
'flow.title': 'Flow recorder',
|
|
193
212
|
'flow.start': 'Record',
|
|
194
213
|
'flow.stop': 'Stop',
|
|
@@ -200,6 +219,21 @@ window.__ModuleLoader__.load({
|
|
|
200
219
|
'flow.saved': 'Saved {name}',
|
|
201
220
|
'flow.done': 'Replayed {name}',
|
|
202
221
|
'flow.count': '{n} step(s)',
|
|
222
|
+
'storage.title': 'Storage',
|
|
223
|
+
'storage.refresh': 'Refresh',
|
|
224
|
+
'storage.cleanTemp': 'Clean temp',
|
|
225
|
+
'storage.cleanSnapshots': 'Clean snapshots',
|
|
226
|
+
'storage.cleanOrphans': 'Clean orphan AVDs',
|
|
227
|
+
'storage.confirm': 'Click again to confirm',
|
|
228
|
+
'storage.freed': 'Freed {size}',
|
|
229
|
+
'storage.avds': '{size} AVDs',
|
|
230
|
+
'storage.snapshots': 'Snapshots',
|
|
231
|
+
'storage.sdk': 'SDK',
|
|
232
|
+
'storage.temp': 'Temp',
|
|
233
|
+
'storage.free': 'Free disk',
|
|
234
|
+
'storage.active': 'active',
|
|
235
|
+
'storage.orphan': 'orphan',
|
|
236
|
+
'storage.empty': 'No AVDs found',
|
|
203
237
|
'apk.bundle': 'Bundle',
|
|
204
238
|
'setup.title': 'Environment',
|
|
205
239
|
'setup.ok': 'Ready',
|
|
@@ -387,6 +421,25 @@ window.__ModuleLoader__.load({
|
|
|
387
421
|
'network.caPath': 'CA 证书路径(.pem/.crt)',
|
|
388
422
|
'network.installCa': '安装 CA',
|
|
389
423
|
'network.current': '当前:{value}',
|
|
424
|
+
'network.forwards': '端口转发',
|
|
425
|
+
'network.forwardLocal': '宿主机端口',
|
|
426
|
+
'network.forwardRemote': 'Guest 端口',
|
|
427
|
+
'network.addForward': '添加转发',
|
|
428
|
+
'network.remove': '删除',
|
|
429
|
+
'network.mode': '模式',
|
|
430
|
+
'network.isolated': '隔离',
|
|
431
|
+
'network.shared': '共享(宿主桥接)',
|
|
432
|
+
'network.dns': 'DNS 服务器',
|
|
433
|
+
'network.applyDns': '应用 DNS',
|
|
434
|
+
'network.hosts': 'Hosts 条目',
|
|
435
|
+
'network.applyHosts': '应用 Hosts',
|
|
436
|
+
'network.test': '测试连通性',
|
|
437
|
+
'network.testOk': '连通性正常',
|
|
438
|
+
'network.testFail': '连通性检查失败',
|
|
439
|
+
'network.pcap': '下载 pcap',
|
|
440
|
+
'network.restartRequired': '重启虚拟机后生效自定义 DNS',
|
|
441
|
+
'network.sharedHint': '共享转发可被其他 VM 通过模拟器宿主别名访问。',
|
|
442
|
+
'network.guestUrl': 'Guest URL: {url}',
|
|
390
443
|
'flow.title': '操作录制',
|
|
391
444
|
'flow.start': '录制',
|
|
392
445
|
'flow.stop': '停止',
|
|
@@ -398,6 +451,21 @@ window.__ModuleLoader__.load({
|
|
|
398
451
|
'flow.saved': '已保存 {name}',
|
|
399
452
|
'flow.done': '已回放 {name}',
|
|
400
453
|
'flow.count': '{n} 步',
|
|
454
|
+
'storage.title': '存储',
|
|
455
|
+
'storage.refresh': '刷新',
|
|
456
|
+
'storage.cleanTemp': '清理临时文件',
|
|
457
|
+
'storage.cleanSnapshots': '清理快照',
|
|
458
|
+
'storage.cleanOrphans': '清理孤立 AVD',
|
|
459
|
+
'storage.confirm': '再点一次确认',
|
|
460
|
+
'storage.freed': '已释放 {size}',
|
|
461
|
+
'storage.avds': '{size} 个 AVD',
|
|
462
|
+
'storage.snapshots': '快照',
|
|
463
|
+
'storage.sdk': 'SDK',
|
|
464
|
+
'storage.temp': '临时文件',
|
|
465
|
+
'storage.free': '磁盘剩余',
|
|
466
|
+
'storage.active': '运行中',
|
|
467
|
+
'storage.orphan': '孤立',
|
|
468
|
+
'storage.empty': '没有找到 AVD',
|
|
401
469
|
'apk.bundle': '安装包',
|
|
402
470
|
'setup.title': '环境',
|
|
403
471
|
'setup.ok': '就绪',
|
|
@@ -1207,6 +1275,19 @@ window.__ModuleLoader__.load({
|
|
|
1207
1275
|
var flowBusy = flowBusyState[0];
|
|
1208
1276
|
var setFlowBusy = flowBusyState[1];
|
|
1209
1277
|
|
|
1278
|
+
var storageState = React.useState(null);
|
|
1279
|
+
var storage = storageState[0];
|
|
1280
|
+
var setStorage = storageState[1];
|
|
1281
|
+
var storageBusyState = React.useState('');
|
|
1282
|
+
var storageBusy = storageBusyState[0];
|
|
1283
|
+
var setStorageBusy = storageBusyState[1];
|
|
1284
|
+
var storageErrorState = React.useState('');
|
|
1285
|
+
var storageError = storageErrorState[0];
|
|
1286
|
+
var setStorageError = storageErrorState[1];
|
|
1287
|
+
var storageConfirmState = React.useState('');
|
|
1288
|
+
var storageConfirm = storageConfirmState[0];
|
|
1289
|
+
var setStorageConfirm = storageConfirmState[1];
|
|
1290
|
+
|
|
1210
1291
|
var proxyState = React.useState({ host: '', port: '' });
|
|
1211
1292
|
var proxy = proxyState[0];
|
|
1212
1293
|
var setProxy = proxyState[1];
|
|
@@ -1216,6 +1297,27 @@ window.__ModuleLoader__.load({
|
|
|
1216
1297
|
var caPathState = React.useState('');
|
|
1217
1298
|
var caPath = caPathState[0];
|
|
1218
1299
|
var setCaPath = caPathState[1];
|
|
1300
|
+
var networkState = React.useState(null);
|
|
1301
|
+
var network = networkState[0];
|
|
1302
|
+
var setNetwork = networkState[1];
|
|
1303
|
+
var networkBusyState = React.useState('');
|
|
1304
|
+
var networkBusy = networkBusyState[0];
|
|
1305
|
+
var setNetworkBusy = networkBusyState[1];
|
|
1306
|
+
var forwardLocalState = React.useState('');
|
|
1307
|
+
var forwardLocal = forwardLocalState[0];
|
|
1308
|
+
var setForwardLocal = forwardLocalState[1];
|
|
1309
|
+
var forwardRemoteState = React.useState('');
|
|
1310
|
+
var forwardRemote = forwardRemoteState[0];
|
|
1311
|
+
var setForwardRemote = forwardRemoteState[1];
|
|
1312
|
+
var forwardSharedState = React.useState(false);
|
|
1313
|
+
var forwardShared = forwardSharedState[0];
|
|
1314
|
+
var setForwardShared = forwardSharedState[1];
|
|
1315
|
+
var dnsTextState = React.useState('');
|
|
1316
|
+
var dnsText = dnsTextState[0];
|
|
1317
|
+
var setDnsText = dnsTextState[1];
|
|
1318
|
+
var hostsTextState = React.useState('');
|
|
1319
|
+
var hostsText = hostsTextState[0];
|
|
1320
|
+
var setHostsText = hostsTextState[1];
|
|
1219
1321
|
|
|
1220
1322
|
var actualFpsState = React.useState(0);
|
|
1221
1323
|
var actualFps = actualFpsState[0];
|
|
@@ -1392,6 +1494,8 @@ window.__ModuleLoader__.load({
|
|
|
1392
1494
|
loadInstances();
|
|
1393
1495
|
loadDevices();
|
|
1394
1496
|
loadFlows();
|
|
1497
|
+
loadStorage();
|
|
1498
|
+
loadNetwork();
|
|
1395
1499
|
var pollId = setInterval(function () {
|
|
1396
1500
|
if (stateBusyRef.current) return;
|
|
1397
1501
|
stateBusyRef.current = true;
|
|
@@ -1413,6 +1517,12 @@ window.__ModuleLoader__.load({
|
|
|
1413
1517
|
};
|
|
1414
1518
|
}, []);
|
|
1415
1519
|
|
|
1520
|
+
// Load network details once the VM becomes ready (the first render often
|
|
1521
|
+
// happens while the state is still loading).
|
|
1522
|
+
React.useEffect(function () {
|
|
1523
|
+
if (ready) loadNetwork();
|
|
1524
|
+
}, [ready, activeInstance]);
|
|
1525
|
+
|
|
1416
1526
|
// Track document visibility so the frame stream pauses when hidden.
|
|
1417
1527
|
React.useEffect(function () {
|
|
1418
1528
|
if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') {
|
|
@@ -1789,6 +1899,8 @@ window.__ModuleLoader__.load({
|
|
|
1789
1899
|
setInspectNodes([]);
|
|
1790
1900
|
setInspectSelected(null);
|
|
1791
1901
|
loadFlows();
|
|
1902
|
+
setNetwork(null);
|
|
1903
|
+
loadNetwork();
|
|
1792
1904
|
stopLiveLogcat();
|
|
1793
1905
|
frameStatsRef.current = [];
|
|
1794
1906
|
loadState();
|
|
@@ -2122,6 +2234,98 @@ window.__ModuleLoader__.load({
|
|
|
2122
2234
|
});
|
|
2123
2235
|
}
|
|
2124
2236
|
|
|
2237
|
+
function loadNetwork() {
|
|
2238
|
+
if (!ready) return Promise.resolve(null);
|
|
2239
|
+
setNetworkBusy('load');
|
|
2240
|
+
return requestJson(instancePath('/network'), null, T).then(function (result) {
|
|
2241
|
+
if (!mountedRef.current) return result;
|
|
2242
|
+
setNetworkBusy('');
|
|
2243
|
+
if (result.ok && isObject(result.data)) {
|
|
2244
|
+
setNetwork(result.data);
|
|
2245
|
+
if (result.data.proxy && typeof result.data.proxy === 'string') {
|
|
2246
|
+
var colon = result.data.proxy.lastIndexOf(':');
|
|
2247
|
+
if (colon > 0) {
|
|
2248
|
+
setProxy({ host: result.data.proxy.slice(0, colon), port: result.data.proxy.slice(colon + 1) });
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
if (result.data.dnsServers && result.data.dnsServers.length) {
|
|
2252
|
+
setDnsText(result.data.dnsServers.join(', '));
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
return result;
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function networkAction(action, payload) {
|
|
2260
|
+
if (networkBusy) return;
|
|
2261
|
+
setNetworkBusy(action);
|
|
2262
|
+
return requestJson(
|
|
2263
|
+
instancePath('/network'),
|
|
2264
|
+
{ method: 'POST', body: JSON.stringify(Object.assign({ action: action }, payload || {})) },
|
|
2265
|
+
T
|
|
2266
|
+
).then(function (result) {
|
|
2267
|
+
if (!mountedRef.current) return result;
|
|
2268
|
+
setNetworkBusy('');
|
|
2269
|
+
if (!result.ok) {
|
|
2270
|
+
pushToast('error', T('err.action') + ': ' + result.error);
|
|
2271
|
+
return result;
|
|
2272
|
+
}
|
|
2273
|
+
if (action === 'test') {
|
|
2274
|
+
pushToast(result.data && result.data.ok === true ? 'success' : 'error', T(result.data && result.data.ok ? 'network.testOk' : 'network.testFail'));
|
|
2275
|
+
} else if (action === 'dns' && result.data && result.data.restartRequired) {
|
|
2276
|
+
pushToast('success', T('network.restartRequired'));
|
|
2277
|
+
} else {
|
|
2278
|
+
pushToast('success', T('network.title'));
|
|
2279
|
+
}
|
|
2280
|
+
loadNetwork();
|
|
2281
|
+
return result;
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function addPortForward() {
|
|
2286
|
+
var local = Number(forwardLocal);
|
|
2287
|
+
var remote = Number(forwardRemote);
|
|
2288
|
+
if (!Number.isInteger(local) || local < 1 || local > 65535 || !Number.isInteger(remote) || remote < 1 || remote > 65535) {
|
|
2289
|
+
pushToast('error', T('err.action'));
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
networkAction('forward-add', {
|
|
2293
|
+
localPort: local,
|
|
2294
|
+
remotePort: remote,
|
|
2295
|
+
shared: forwardShared === true,
|
|
2296
|
+
}).then(function (result) {
|
|
2297
|
+
if (result && result.ok) {
|
|
2298
|
+
setForwardLocal('');
|
|
2299
|
+
setForwardRemote('');
|
|
2300
|
+
setForwardShared(false);
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
function removePortForward(localPort) {
|
|
2306
|
+
networkAction('forward-remove', { localPort: localPort });
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
function applyDns() {
|
|
2310
|
+
var servers = String(dnsText || '')
|
|
2311
|
+
.split(',')
|
|
2312
|
+
.map(function (item) {
|
|
2313
|
+
return item.trim();
|
|
2314
|
+
})
|
|
2315
|
+
.filter(Boolean);
|
|
2316
|
+
networkAction('dns', { servers: servers });
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
function applyHosts() {
|
|
2320
|
+
var entries = String(hostsText || '')
|
|
2321
|
+
.split(/\r?\n/)
|
|
2322
|
+
.map(function (line) {
|
|
2323
|
+
return line.trim();
|
|
2324
|
+
})
|
|
2325
|
+
.filter(Boolean);
|
|
2326
|
+
networkAction('hosts', { entries: entries });
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2125
2329
|
function applyProxy() {
|
|
2126
2330
|
if (proxyBusy) return;
|
|
2127
2331
|
setProxyBusy(true);
|
|
@@ -2137,6 +2341,7 @@ window.__ModuleLoader__.load({
|
|
|
2137
2341
|
return;
|
|
2138
2342
|
}
|
|
2139
2343
|
pushToast('success', T('network.current', { value: (result.data && result.data.proxy) || 'off' }));
|
|
2344
|
+
loadNetwork();
|
|
2140
2345
|
});
|
|
2141
2346
|
}
|
|
2142
2347
|
|
|
@@ -2151,6 +2356,7 @@ window.__ModuleLoader__.load({
|
|
|
2151
2356
|
return;
|
|
2152
2357
|
}
|
|
2153
2358
|
pushToast('success', T('network.clear'));
|
|
2359
|
+
loadNetwork();
|
|
2154
2360
|
});
|
|
2155
2361
|
}
|
|
2156
2362
|
|
|
@@ -2173,6 +2379,67 @@ window.__ModuleLoader__.load({
|
|
|
2173
2379
|
});
|
|
2174
2380
|
}
|
|
2175
2381
|
|
|
2382
|
+
function loadStorage() {
|
|
2383
|
+
setStorageBusy('load');
|
|
2384
|
+
setStorageError('');
|
|
2385
|
+
return requestJson('/apk-vm/storage', null, T).then(function (result) {
|
|
2386
|
+
if (!mountedRef.current) return result;
|
|
2387
|
+
setStorageBusy('');
|
|
2388
|
+
if (!result.ok) {
|
|
2389
|
+
setStorageError(result.error || T('err.action'));
|
|
2390
|
+
return result;
|
|
2391
|
+
}
|
|
2392
|
+
setStorage(isObject(result.data) ? result.data : null);
|
|
2393
|
+
return result;
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
function storageSnapshotAvd() {
|
|
2398
|
+
var list = storage && Array.isArray(storage.avds) ? storage.avds : [];
|
|
2399
|
+
var current = snapshot && snapshot.vm && snapshot.vm.avd ? String(snapshot.vm.avd) : '';
|
|
2400
|
+
var match = list.find(function (item) {
|
|
2401
|
+
return item.name === current;
|
|
2402
|
+
});
|
|
2403
|
+
if (match) return match.name;
|
|
2404
|
+
var active = list.find(function (item) {
|
|
2405
|
+
return item.active && item.managed;
|
|
2406
|
+
});
|
|
2407
|
+
if (active) return active.name;
|
|
2408
|
+
var managed = list.find(function (item) {
|
|
2409
|
+
return item.managed;
|
|
2410
|
+
});
|
|
2411
|
+
return managed ? managed.name : 'dsh_apk';
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
function cleanStorage(action) {
|
|
2415
|
+
if (storageBusy) return;
|
|
2416
|
+
var destructive = action === 'snapshots' || action === 'orphan-avds';
|
|
2417
|
+
if (destructive && storageConfirm !== action) {
|
|
2418
|
+
setStorageConfirm(action);
|
|
2419
|
+
pushToast('success', T('storage.confirm'));
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
setStorageConfirm('');
|
|
2423
|
+
setStorageBusy(action);
|
|
2424
|
+
var payload = { action: action };
|
|
2425
|
+
if (action === 'snapshots') {
|
|
2426
|
+
payload.avdName = storageSnapshotAvd();
|
|
2427
|
+
payload.confirm = true;
|
|
2428
|
+
}
|
|
2429
|
+
if (action === 'orphan-avds') payload.confirm = true;
|
|
2430
|
+
requestJson('/apk-vm/storage', { method: 'POST', body: JSON.stringify(payload) }, T).then(function (result) {
|
|
2431
|
+
if (!mountedRef.current) return;
|
|
2432
|
+
setStorageBusy('');
|
|
2433
|
+
if (!result.ok) {
|
|
2434
|
+
pushToast('error', T('err.action') + ': ' + result.error);
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
var freed = result.data && Number(result.data.freedBytes) ? Number(result.data.freedBytes) : 0;
|
|
2438
|
+
pushToast('success', T('storage.freed', { size: formatBytes(freed) }));
|
|
2439
|
+
loadStorage();
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2176
2443
|
function refreshAll() {
|
|
2177
2444
|
loadState();
|
|
2178
2445
|
loadApks();
|
|
@@ -3810,7 +4077,285 @@ window.__ModuleLoader__.load({
|
|
|
3810
4077
|
onClick: installCa,
|
|
3811
4078
|
})
|
|
3812
4079
|
),
|
|
3813
|
-
h('div', { className: 'apkvm-dim' }, T('network.caPath'))
|
|
4080
|
+
h('div', { className: 'apkvm-dim' }, T('network.caPath')),
|
|
4081
|
+
h(
|
|
4082
|
+
'label',
|
|
4083
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
4084
|
+
h('span', null, T('network.mode')),
|
|
4085
|
+
h(
|
|
4086
|
+
'select',
|
|
4087
|
+
{
|
|
4088
|
+
value: (network && network.mode) || 'isolated',
|
|
4089
|
+
disabled: Boolean(networkBusy) || !ready,
|
|
4090
|
+
onChange: function (event) {
|
|
4091
|
+
networkAction('mode', { mode: event && event.target ? String(event.target.value) : 'isolated' });
|
|
4092
|
+
},
|
|
4093
|
+
style: {
|
|
4094
|
+
font: 'inherit',
|
|
4095
|
+
background: 'var(--dsw-alias-bg-layer-1)',
|
|
4096
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
4097
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
4098
|
+
borderRadius: '6px',
|
|
4099
|
+
padding: '1px 4px',
|
|
4100
|
+
},
|
|
4101
|
+
},
|
|
4102
|
+
h('option', { value: 'isolated' }, T('network.isolated')),
|
|
4103
|
+
h('option', { value: 'shared' }, T('network.shared'))
|
|
4104
|
+
)
|
|
4105
|
+
),
|
|
4106
|
+
h(
|
|
4107
|
+
'div',
|
|
4108
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap' } },
|
|
4109
|
+
h('span', { className: 'apkvm-card-title' }, T('network.forwards')),
|
|
4110
|
+
actionBtn(T('network.test'), {
|
|
4111
|
+
key: 'network-test',
|
|
4112
|
+
small: true,
|
|
4113
|
+
disabled: !ready || Boolean(networkBusy),
|
|
4114
|
+
onClick: function () {
|
|
4115
|
+
networkAction('test', { host: proxy.host || '', port: proxy.port || undefined });
|
|
4116
|
+
},
|
|
4117
|
+
})
|
|
4118
|
+
),
|
|
4119
|
+
network && Array.isArray(network.forwards) && network.forwards.length
|
|
4120
|
+
? network.forwards.map(function (rule) {
|
|
4121
|
+
return h(
|
|
4122
|
+
'div',
|
|
4123
|
+
{ key: 'forward-' + rule.localPort, className: 'apkvm-dim', style: { display: 'flex', alignItems: 'center', gap: '5px' } },
|
|
4124
|
+
h('span', { style: { flex: '1 1 auto' } }, 'tcp:' + rule.localPort + ' -> guest tcp:' + rule.remotePort),
|
|
4125
|
+
rule.shared ? h('span', { className: 'apkvm-tag ok' }, 'shared') : null,
|
|
4126
|
+
actionBtn(T('network.remove'), {
|
|
4127
|
+
key: 'forward-remove-' + rule.localPort,
|
|
4128
|
+
small: true,
|
|
4129
|
+
variant: 'danger',
|
|
4130
|
+
disabled: Boolean(networkBusy),
|
|
4131
|
+
onClick: function () {
|
|
4132
|
+
removePortForward(rule.localPort);
|
|
4133
|
+
},
|
|
4134
|
+
})
|
|
4135
|
+
);
|
|
4136
|
+
})
|
|
4137
|
+
: h('div', { className: 'apkvm-dim' }, T('network.forwards')),
|
|
4138
|
+
h(
|
|
4139
|
+
'div',
|
|
4140
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '5px' } },
|
|
4141
|
+
h('input', {
|
|
4142
|
+
className: 'apkvm-input',
|
|
4143
|
+
type: 'text',
|
|
4144
|
+
value: forwardLocal,
|
|
4145
|
+
placeholder: T('network.forwardLocal'),
|
|
4146
|
+
spellCheck: false,
|
|
4147
|
+
disabled: Boolean(networkBusy),
|
|
4148
|
+
onChange: function (event) {
|
|
4149
|
+
setForwardLocal(event && event.target ? String(event.target.value) : '');
|
|
4150
|
+
},
|
|
4151
|
+
'aria-label': T('network.forwardLocal'),
|
|
4152
|
+
}),
|
|
4153
|
+
h('input', {
|
|
4154
|
+
className: 'apkvm-input',
|
|
4155
|
+
type: 'text',
|
|
4156
|
+
value: forwardRemote,
|
|
4157
|
+
placeholder: T('network.forwardRemote'),
|
|
4158
|
+
spellCheck: false,
|
|
4159
|
+
disabled: Boolean(networkBusy),
|
|
4160
|
+
onChange: function (event) {
|
|
4161
|
+
setForwardRemote(event && event.target ? String(event.target.value) : '');
|
|
4162
|
+
},
|
|
4163
|
+
'aria-label': T('network.forwardRemote'),
|
|
4164
|
+
}),
|
|
4165
|
+
actionBtn(T('network.addForward'), {
|
|
4166
|
+
key: 'forward-add',
|
|
4167
|
+
small: true,
|
|
4168
|
+
disabled: !ready || Boolean(networkBusy) || !forwardLocal.trim() || !forwardRemote.trim(),
|
|
4169
|
+
onClick: addPortForward,
|
|
4170
|
+
})
|
|
4171
|
+
),
|
|
4172
|
+
h(
|
|
4173
|
+
'label',
|
|
4174
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '5px', fontSize: '11px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
4175
|
+
h('input', {
|
|
4176
|
+
type: 'checkbox',
|
|
4177
|
+
checked: forwardShared,
|
|
4178
|
+
disabled: Boolean(networkBusy),
|
|
4179
|
+
onChange: function (event) {
|
|
4180
|
+
setForwardShared(Boolean(event && event.target && event.target.checked));
|
|
4181
|
+
},
|
|
4182
|
+
}),
|
|
4183
|
+
h('span', null, T('network.shared'))
|
|
4184
|
+
),
|
|
4185
|
+
h(
|
|
4186
|
+
'div',
|
|
4187
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '5px' } },
|
|
4188
|
+
h('input', {
|
|
4189
|
+
className: 'apkvm-input',
|
|
4190
|
+
type: 'text',
|
|
4191
|
+
value: dnsText,
|
|
4192
|
+
placeholder: T('network.dns'),
|
|
4193
|
+
spellCheck: false,
|
|
4194
|
+
disabled: Boolean(networkBusy),
|
|
4195
|
+
onChange: function (event) {
|
|
4196
|
+
setDnsText(event && event.target ? String(event.target.value) : '');
|
|
4197
|
+
},
|
|
4198
|
+
'aria-label': T('network.dns'),
|
|
4199
|
+
}),
|
|
4200
|
+
actionBtn(T('network.applyDns'), {
|
|
4201
|
+
key: 'dns-apply',
|
|
4202
|
+
small: true,
|
|
4203
|
+
disabled: !ready || Boolean(networkBusy),
|
|
4204
|
+
onClick: applyDns,
|
|
4205
|
+
})
|
|
4206
|
+
),
|
|
4207
|
+
h('textarea', {
|
|
4208
|
+
className: 'apkvm-input',
|
|
4209
|
+
value: hostsText,
|
|
4210
|
+
placeholder: T('network.hosts'),
|
|
4211
|
+
spellCheck: false,
|
|
4212
|
+
disabled: Boolean(networkBusy),
|
|
4213
|
+
style: { minHeight: '46px', height: '46px', resize: 'vertical', padding: '4px 8px' },
|
|
4214
|
+
onChange: function (event) {
|
|
4215
|
+
setHostsText(event && event.target ? String(event.target.value) : '');
|
|
4216
|
+
},
|
|
4217
|
+
'aria-label': T('network.hosts'),
|
|
4218
|
+
}),
|
|
4219
|
+
h(
|
|
4220
|
+
'div',
|
|
4221
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap' } },
|
|
4222
|
+
actionBtn(T('network.applyHosts'), {
|
|
4223
|
+
key: 'hosts-apply',
|
|
4224
|
+
small: true,
|
|
4225
|
+
disabled: !ready || Boolean(networkBusy),
|
|
4226
|
+
onClick: applyHosts,
|
|
4227
|
+
}),
|
|
4228
|
+
network && network.pcap && network.pcap.enabled
|
|
4229
|
+
? h(
|
|
4230
|
+
'a',
|
|
4231
|
+
{
|
|
4232
|
+
href: instancePath('/network/pcap'),
|
|
4233
|
+
download: 'capture.pcap',
|
|
4234
|
+
style: { fontSize: '11px', color: 'var(--dsw-alias-brand-primary)' },
|
|
4235
|
+
},
|
|
4236
|
+
T('network.pcap')
|
|
4237
|
+
)
|
|
4238
|
+
: null
|
|
4239
|
+
),
|
|
4240
|
+
network && network.mode === 'shared' ? h('div', { className: 'apkvm-dim' }, T('network.sharedHint')) : null
|
|
4241
|
+
)
|
|
4242
|
+
);
|
|
4243
|
+
|
|
4244
|
+
var storageCard = h(
|
|
4245
|
+
'details',
|
|
4246
|
+
{ className: 'apkvm-card apkvm-debug' },
|
|
4247
|
+
h(
|
|
4248
|
+
'summary',
|
|
4249
|
+
{ className: 'apkvm-debug-summary' },
|
|
4250
|
+
h('span', { className: 'apkvm-card-title' }, T('storage.title')),
|
|
4251
|
+
h(
|
|
4252
|
+
'span',
|
|
4253
|
+
{ className: 'apkvm-dim', style: { marginLeft: 'auto' } },
|
|
4254
|
+
storage ? formatBytes((storage.totals && storage.totals.avdBytes) || 0) : '…'
|
|
4255
|
+
),
|
|
4256
|
+
h('span', { className: 'apkvm-chevron', 'aria-hidden': 'true' }, '▸')
|
|
4257
|
+
),
|
|
4258
|
+
h(
|
|
4259
|
+
'div',
|
|
4260
|
+
{ className: 'apkvm-debug-body' },
|
|
4261
|
+
!storage
|
|
4262
|
+
? h('div', { className: 'apkvm-dim' }, storageError || T('storage.empty'))
|
|
4263
|
+
: h(
|
|
4264
|
+
'div',
|
|
4265
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '5px', minWidth: 0 } },
|
|
4266
|
+
h(
|
|
4267
|
+
'div',
|
|
4268
|
+
{ className: 'apkvm-dim', style: { display: 'flex', flexWrap: 'wrap', gap: '8px' } },
|
|
4269
|
+
h('span', null, T('storage.avds', { size: (storage.avds || []).length }) + ': ' + formatBytes((storage.totals && storage.totals.avdBytes) || 0)),
|
|
4270
|
+
h('span', null, T('storage.snapshots') + ': ' + formatBytes((storage.totals && storage.totals.snapshotBytes) || 0)),
|
|
4271
|
+
h('span', null, T('storage.sdk') + ': ' + formatBytes((storage.totals && storage.totals.sdkBytes) || 0)),
|
|
4272
|
+
h('span', null, T('storage.temp') + ': ' + formatBytes((storage.totals && storage.totals.tempBytes) || 0)),
|
|
4273
|
+
storage.disk
|
|
4274
|
+
? h('span', null, T('storage.free') + ': ' + formatBytes(storage.disk.freeBytes))
|
|
4275
|
+
: null
|
|
4276
|
+
),
|
|
4277
|
+
(storage.avds || []).slice(0, 4).map(function (avd) {
|
|
4278
|
+
return h(
|
|
4279
|
+
'div',
|
|
4280
|
+
{
|
|
4281
|
+
key: 'storage-' + avd.name,
|
|
4282
|
+
className: 'apkvm-apk',
|
|
4283
|
+
style: { padding: '5px 7px', gap: '3px' },
|
|
4284
|
+
},
|
|
4285
|
+
h(
|
|
4286
|
+
'div',
|
|
4287
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 } },
|
|
4288
|
+
h('span', { className: 'apkvm-apk-name', title: avd.path }, avd.name),
|
|
4289
|
+
avd.active ? h('span', { className: 'apkvm-tag ok' }, T('storage.active')) : null,
|
|
4290
|
+
avd.orphan ? h('span', { className: 'apkvm-tag' }, T('storage.orphan')) : null
|
|
4291
|
+
),
|
|
4292
|
+
h(
|
|
4293
|
+
'div',
|
|
4294
|
+
{ className: 'apkvm-dim' },
|
|
4295
|
+
formatBytes(avd.totalBytes || 0) +
|
|
4296
|
+
' · ' +
|
|
4297
|
+
T('storage.snapshots') +
|
|
4298
|
+
' ' +
|
|
4299
|
+
formatBytes(avd.snapshotBytes || 0)
|
|
4300
|
+
)
|
|
4301
|
+
);
|
|
4302
|
+
}),
|
|
4303
|
+
h(
|
|
4304
|
+
'div',
|
|
4305
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap' } },
|
|
4306
|
+
actionBtn(storageBusy === 'load' ? '…' : T('storage.refresh'), {
|
|
4307
|
+
key: 'storage-refresh',
|
|
4308
|
+
small: true,
|
|
4309
|
+
disabled: Boolean(storageBusy),
|
|
4310
|
+
onClick: loadStorage,
|
|
4311
|
+
}),
|
|
4312
|
+
actionBtn(storageBusy === 'temp' ? '…' : T('storage.cleanTemp'), {
|
|
4313
|
+
key: 'storage-temp',
|
|
4314
|
+
small: true,
|
|
4315
|
+
disabled: Boolean(storageBusy),
|
|
4316
|
+
onClick: function () {
|
|
4317
|
+
cleanStorage('temp');
|
|
4318
|
+
},
|
|
4319
|
+
}),
|
|
4320
|
+
actionBtn(
|
|
4321
|
+
storageConfirm === 'snapshots'
|
|
4322
|
+
? T('storage.confirm')
|
|
4323
|
+
: storageBusy === 'snapshots'
|
|
4324
|
+
? '…'
|
|
4325
|
+
: T('storage.cleanSnapshots'),
|
|
4326
|
+
{
|
|
4327
|
+
key: 'storage-snapshots',
|
|
4328
|
+
small: true,
|
|
4329
|
+
disabled: Boolean(storageBusy),
|
|
4330
|
+
onClick: function () {
|
|
4331
|
+
cleanStorage('snapshots');
|
|
4332
|
+
},
|
|
4333
|
+
}
|
|
4334
|
+
),
|
|
4335
|
+
(storage.recommendations || []).some(function (item) {
|
|
4336
|
+
return item.code === 'orphan-avds';
|
|
4337
|
+
}) || (storage.avds || []).some(function (item) {
|
|
4338
|
+
return item.orphan;
|
|
4339
|
+
})
|
|
4340
|
+
? actionBtn(
|
|
4341
|
+
storageConfirm === 'orphan-avds'
|
|
4342
|
+
? T('storage.confirm')
|
|
4343
|
+
: storageBusy === 'orphan-avds'
|
|
4344
|
+
? '…'
|
|
4345
|
+
: T('storage.cleanOrphans'),
|
|
4346
|
+
{
|
|
4347
|
+
key: 'storage-orphans',
|
|
4348
|
+
small: true,
|
|
4349
|
+
disabled: Boolean(storageBusy),
|
|
4350
|
+
onClick: function () {
|
|
4351
|
+
cleanStorage('orphan-avds');
|
|
4352
|
+
},
|
|
4353
|
+
}
|
|
4354
|
+
)
|
|
4355
|
+
: null
|
|
4356
|
+
),
|
|
4357
|
+
storageError ? h('div', { className: 'apkvm-dim' }, storageError) : null
|
|
4358
|
+
)
|
|
3814
4359
|
)
|
|
3815
4360
|
);
|
|
3816
4361
|
|
|
@@ -4149,6 +4694,7 @@ window.__ModuleLoader__.load({
|
|
|
4149
4694
|
debugCard,
|
|
4150
4695
|
flowCard,
|
|
4151
4696
|
networkCard,
|
|
4697
|
+
storageCard,
|
|
4152
4698
|
appsCard,
|
|
4153
4699
|
diagCard,
|
|
4154
4700
|
h('div', { className: 'apkvm-footnote', title: T('hint.boot') }, T('hint.short')),
|