Wire device execution mode controls into UI
This commit is contained in:
@@ -401,6 +401,31 @@ public class BossApiClient {
|
||||
return requestWithRestore("PATCH", "/api/v1/devices/" + encode(deviceId), payload);
|
||||
}
|
||||
|
||||
public ApiResponse updateDevicePreferredExecutionMode(
|
||||
String deviceId,
|
||||
@Nullable String preferredExecutionMode
|
||||
) throws IOException, JSONException {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put(
|
||||
"preferredExecutionMode",
|
||||
preferredExecutionMode == null ? JSONObject.NULL : preferredExecutionMode
|
||||
);
|
||||
return updateDevice(deviceId, payload);
|
||||
}
|
||||
|
||||
public ApiResponse updateProjectConflictDecision(
|
||||
String deviceId,
|
||||
String projectId,
|
||||
@Nullable String folderKey,
|
||||
String decision
|
||||
) throws IOException, JSONException {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("projectId", projectId);
|
||||
payload.put("folderKey", folderKey == null ? JSONObject.NULL : folderKey);
|
||||
payload.put("conflictDecision", decision);
|
||||
return updateDevice(deviceId, payload);
|
||||
}
|
||||
|
||||
public ApiResponse getDeviceSkills(String deviceId) throws IOException, JSONException {
|
||||
return requestWithRestore("GET", "/api/v1/devices/" + encode(deviceId) + "/skills", null);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class DeviceDetailActivity extends BossScreenActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
deviceId = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
||||
deviceName = getIntent().getStringExtra(EXTRA_DEVICE_NAME);
|
||||
configureScreen(deviceName == null ? "设备详情" : deviceName, "设备状态与绑定项目");
|
||||
configureScreen(deviceName == null ? "设备详情" : deviceName, "设备状态、GUI/CLI 能力与默认执行模式");
|
||||
setHeaderAction("编辑", v -> openEditDialog());
|
||||
reload();
|
||||
}
|
||||
@@ -47,6 +47,7 @@ public class DeviceDetailActivity extends BossScreenActivity {
|
||||
private void renderDevice(JSONObject payload) {
|
||||
JSONObject workspace = payload.optJSONObject("workspace");
|
||||
JSONObject device = workspace == null ? null : workspace.optJSONObject("selectedDevice");
|
||||
JSONObject primaryPolicy = resolvePrimaryProjectExecutionPolicy(workspace);
|
||||
|
||||
replaceContent();
|
||||
if (device == null) {
|
||||
@@ -56,7 +57,7 @@ public class DeviceDetailActivity extends BossScreenActivity {
|
||||
}
|
||||
|
||||
deviceName = device.optString("name", deviceId);
|
||||
configureScreen(deviceName, "设备状态与绑定项目");
|
||||
configureScreen(deviceName, "设备状态、GUI/CLI 能力与默认执行模式");
|
||||
WechatSurfaceMapper.DeviceDetailSummary summary = WechatSurfaceMapper.toDeviceDetailSummary(device);
|
||||
appendContent(BossUi.buildDeviceCard(
|
||||
this,
|
||||
@@ -74,6 +75,64 @@ public class DeviceDetailActivity extends BossScreenActivity {
|
||||
null
|
||||
));
|
||||
}
|
||||
appendContent(BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
WechatSurfaceMapper.deviceCapabilityTitle("gui"),
|
||||
WechatSurfaceMapper.deviceCapabilityStatusLabel(device, "gui"),
|
||||
WechatSurfaceMapper.deviceCapabilityDetailLabel(device, "gui"),
|
||||
null,
|
||||
null
|
||||
));
|
||||
appendContent(BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
WechatSurfaceMapper.deviceCapabilityTitle("cli"),
|
||||
WechatSurfaceMapper.deviceCapabilityStatusLabel(device, "cli"),
|
||||
WechatSurfaceMapper.deviceCapabilityDetailLabel(device, "cli"),
|
||||
null,
|
||||
null
|
||||
));
|
||||
appendContent(BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
"默认执行模式",
|
||||
WechatSurfaceMapper.devicePreferredExecutionModeSummary(device),
|
||||
"切换",
|
||||
null,
|
||||
v -> showPreferredExecutionModeDialog(device)
|
||||
));
|
||||
if (primaryPolicy != null) {
|
||||
appendContent(BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
"异常项目 / 文件夹冲突",
|
||||
primaryPolicy.optString("projectId", "未知项目"),
|
||||
primaryPolicy.optString("folderKey", ""),
|
||||
null,
|
||||
null
|
||||
));
|
||||
appendContent(BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
"当前冲突态",
|
||||
WechatSurfaceMapper.projectConflictStateLabel(primaryPolicy.optString("conflictState", "")),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
));
|
||||
appendContent(BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
"当前策略",
|
||||
WechatSurfaceMapper.projectConflictAllowPolicyLabel(primaryPolicy.optString("allowPolicy", "")),
|
||||
"仅作用于当前异常项目 / 文件夹",
|
||||
null,
|
||||
null
|
||||
));
|
||||
appendContent(BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
"冲突策略",
|
||||
"禁止 / 允许本次 / 永久放行",
|
||||
"切换",
|
||||
null,
|
||||
v -> showConflictDecisionDialog(payload)
|
||||
));
|
||||
}
|
||||
appendContent(BossUi.buildMenuRow(this, "导入项目", "勾选这台设备上要暴露到会话首页的项目和线程", null, v -> openImportDraft()));
|
||||
appendContent(BossUi.buildMenuRow(this, "查看技能", "查看当前设备同步的 Skill 清单", null, v -> openSkills()));
|
||||
setRefreshing(false);
|
||||
@@ -93,6 +152,50 @@ public class DeviceDetailActivity extends BossScreenActivity {
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void showPreferredExecutionModeDialog(JSONObject device) {
|
||||
String currentMode = device == null ? "cli" : device.optString("preferredExecutionMode", "cli");
|
||||
String[] modeLabels = new String[] {
|
||||
WechatSurfaceMapper.deviceExecutionModeChoiceLabel("gui"),
|
||||
WechatSurfaceMapper.deviceExecutionModeChoiceLabel("cli")
|
||||
};
|
||||
String[] modeValues = new String[] {"gui", "cli"};
|
||||
int checkedIndex = "gui".equals(currentMode) ? 0 : 1;
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("默认执行模式")
|
||||
.setSingleChoiceItems(modeLabels, checkedIndex, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
savePreferredExecutionMode(modeValues[which]);
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showConflictDecisionDialog(JSONObject payload) {
|
||||
JSONObject workspace = payload == null ? null : payload.optJSONObject("workspace");
|
||||
JSONObject primaryPolicy = resolvePrimaryProjectExecutionPolicy(workspace);
|
||||
if (primaryPolicy == null) {
|
||||
showMessage("当前没有可处理的异常项目 / 文件夹。");
|
||||
return;
|
||||
}
|
||||
String[] labels = new String[] {"禁止", "允许本次", "永久放行"};
|
||||
String[] values = new String[] {"forbid", "allow_once", "allow_always"};
|
||||
int checkedIndex = resolveConflictDecisionCheckedIndex(primaryPolicy.optString("allowPolicy", ""));
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("冲突策略")
|
||||
.setSingleChoiceItems(labels, checkedIndex, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
saveConflictDecision(
|
||||
primaryPolicy.optString("projectId", ""),
|
||||
primaryPolicy.optString("folderKey", ""),
|
||||
values[which]
|
||||
);
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void openEditDialog() {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
@@ -108,6 +211,52 @@ public class DeviceDetailActivity extends BossScreenActivity {
|
||||
});
|
||||
}
|
||||
|
||||
private void savePreferredExecutionMode(String preferredExecutionMode) {
|
||||
setRefreshing(true);
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
BossApiClient.ApiResponse response = apiClient.updateDevicePreferredExecutionMode(
|
||||
deviceId,
|
||||
preferredExecutionMode
|
||||
);
|
||||
if (!response.ok()) throw new IllegalStateException(response.message());
|
||||
runOnUiThread(() -> {
|
||||
showMessage("默认执行模式已更新");
|
||||
reload();
|
||||
});
|
||||
} catch (Exception error) {
|
||||
runOnUiThread(() -> {
|
||||
setRefreshing(false);
|
||||
showMessage("保存失败:" + error.getMessage());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void saveConflictDecision(String projectId, @Nullable String folderKey, String decision) {
|
||||
setRefreshing(true);
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
BossApiClient.ApiResponse response = apiClient.updateProjectConflictDecision(
|
||||
deviceId,
|
||||
projectId,
|
||||
folderKey,
|
||||
decision
|
||||
);
|
||||
if (!response.ok()) throw new IllegalStateException(response.message());
|
||||
runOnUiThread(() -> {
|
||||
showMessage("冲突策略已更新");
|
||||
reload();
|
||||
});
|
||||
} catch (Exception error) {
|
||||
runOnUiThread(() -> {
|
||||
setRefreshing(false);
|
||||
showMessage("保存失败:" + error.getMessage());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showEditForm(JSONObject device) {
|
||||
LinearLayout form = new LinearLayout(this);
|
||||
form.setOrientation(LinearLayout.VERTICAL);
|
||||
@@ -185,4 +334,21 @@ public class DeviceDetailActivity extends BossScreenActivity {
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private @Nullable JSONObject resolvePrimaryProjectExecutionPolicy(@Nullable JSONObject workspace) {
|
||||
if (workspace == null) return null;
|
||||
JSONArray policies = workspace.optJSONArray("projectExecutionPolicies");
|
||||
if (policies == null || policies.length() == 0) return null;
|
||||
return policies.optJSONObject(0);
|
||||
}
|
||||
|
||||
private int resolveConflictDecisionCheckedIndex(String allowPolicy) {
|
||||
if ("allow_once".equals(allowPolicy)) {
|
||||
return 1;
|
||||
}
|
||||
if ("allow_always".equals(allowPolicy)) {
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,71 @@ public final class WechatSurfaceMapper {
|
||||
);
|
||||
}
|
||||
|
||||
public static String deviceCapabilityTitle(String capabilityKey) {
|
||||
return "gui".equals(capabilityKey) ? "GUI 能力" : "CLI 能力";
|
||||
}
|
||||
|
||||
public static String deviceCapabilityStatusLabel(JSONObject device, String capabilityKey) {
|
||||
JSONObject capability = resolveDeviceCapability(device, capabilityKey);
|
||||
boolean connected = capability != null && capability.optBoolean("connected", false);
|
||||
return connected ? "已连接" : "未连接";
|
||||
}
|
||||
|
||||
public static String deviceCapabilityDetailLabel(JSONObject device, String capabilityKey) {
|
||||
JSONObject capability = resolveDeviceCapability(device, capabilityKey);
|
||||
if (capability == null) {
|
||||
return "暂无最近上报";
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String lastSeenAt = capability.optString("lastSeenAt", "").trim();
|
||||
String lastActiveProjectId = capability.optString("lastActiveProjectId", "").trim();
|
||||
if (!lastSeenAt.isEmpty()) {
|
||||
builder.append("最近上报 ").append(lastSeenAt);
|
||||
}
|
||||
if (!lastActiveProjectId.isEmpty()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(" · ");
|
||||
}
|
||||
builder.append("最近项目 ").append(lastActiveProjectId);
|
||||
}
|
||||
if (builder.length() == 0) {
|
||||
return "暂无最近上报";
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String devicePreferredExecutionModeLabel(JSONObject device) {
|
||||
return "gui".equals(device == null ? "" : device.optString("preferredExecutionMode", "")) ? "GUI" : "CLI";
|
||||
}
|
||||
|
||||
public static String devicePreferredExecutionModeSummary(JSONObject device) {
|
||||
return "当前默认:" + devicePreferredExecutionModeLabel(device);
|
||||
}
|
||||
|
||||
public static String deviceExecutionModeChoiceLabel(String mode) {
|
||||
return "gui".equals(mode) ? "GUI" : "CLI";
|
||||
}
|
||||
|
||||
public static String projectConflictAllowPolicyLabel(String allowPolicy) {
|
||||
if ("allow_once".equals(allowPolicy)) {
|
||||
return "允许本次";
|
||||
}
|
||||
if ("allow_always".equals(allowPolicy)) {
|
||||
return "永久放行";
|
||||
}
|
||||
return "禁止";
|
||||
}
|
||||
|
||||
public static String projectConflictStateLabel(String conflictState) {
|
||||
if ("warning".equals(conflictState)) {
|
||||
return "存在并行风险";
|
||||
}
|
||||
if ("blocked".equals(conflictState)) {
|
||||
return "默认阻断";
|
||||
}
|
||||
return "暂无冲突";
|
||||
}
|
||||
|
||||
public static String[] rootTabLabels() {
|
||||
return ROOT_TAB_LABELS.toArray(new String[0]);
|
||||
}
|
||||
@@ -235,6 +300,17 @@ public final class WechatSurfaceMapper {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static JSONObject resolveDeviceCapability(JSONObject device, String capabilityKey) {
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject capabilities = device.optJSONObject("capabilities");
|
||||
if (capabilities == null) {
|
||||
return null;
|
||||
}
|
||||
return capabilities.optJSONObject(capabilityKey);
|
||||
}
|
||||
|
||||
public static RootTopAction rootTopAction(String activeTab, boolean refreshing) {
|
||||
return rootTopAction(activeTab, refreshing, false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user