This is an automated email from the ASF dual-hosted git repository.
rickyma pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-uniffle.git
The following commit(s) were added to refs/heads/master by this push:
new 77e8612f9 [#1401] feat(dashboard): Support removing excluded servers
(#1969)
77e8612f9 is described below
commit 77e8612f96d10bd9d438d069081c3d7944ced11b
Author: yl09099 <[email protected]>
AuthorDate: Fri Aug 2 20:32:04 2024 +0800
[#1401] feat(dashboard): Support removing excluded servers (#1969)
### What changes were proposed in this pull request?
1. Provide the page deletion function for blacklist pages.
2. Provide search function for blacklist page.


### Why are the changes needed?
Fix: #1401
### Does this PR introduce _any_ user-facing change?
Yes.
### How was this patch tested?
UT.
---
.../apache/uniffle/coordinator/ClusterManager.java | 2 +
.../uniffle/coordinator/SimpleClusterManager.java | 28 ++++
.../coordinator/web/resource/ServerResource.java | 11 ++
dashboard/src/main/webapp/src/api/api.js | 5 +
dashboard/src/main/webapp/src/main.js | 2 +-
.../src/pages/serverstatus/ExcludeNodeList.vue | 181 ++++++++++++++++-----
6 files changed, 190 insertions(+), 39 deletions(-)
diff --git
a/coordinator/src/main/java/org/apache/uniffle/coordinator/ClusterManager.java
b/coordinator/src/main/java/org/apache/uniffle/coordinator/ClusterManager.java
index d4a80122d..1990213b3 100644
---
a/coordinator/src/main/java/org/apache/uniffle/coordinator/ClusterManager.java
+++
b/coordinator/src/main/java/org/apache/uniffle/coordinator/ClusterManager.java
@@ -84,4 +84,6 @@ public interface ClusterManager extends Closeable {
/** Add blacklist. */
boolean addExcludedNodes(List<String> excludedNodeIds);
+
+ boolean removeExcludedNodesFromFile(List<String> excludedNodeIds);
}
diff --git
a/coordinator/src/main/java/org/apache/uniffle/coordinator/SimpleClusterManager.java
b/coordinator/src/main/java/org/apache/uniffle/coordinator/SimpleClusterManager.java
index 3c27b8d89..1bd73dbe4 100644
---
a/coordinator/src/main/java/org/apache/uniffle/coordinator/SimpleClusterManager.java
+++
b/coordinator/src/main/java/org/apache/uniffle/coordinator/SimpleClusterManager.java
@@ -270,6 +270,21 @@ public class SimpleClusterManager implements
ClusterManager {
return true;
}
+ private synchronized boolean removeExcludedNodesFile(List<String>
excludedNodes)
+ throws IOException {
+ if (hadoopFileSystem == null) {
+ return false;
+ }
+ Path hadoopPath = new Path(excludedNodesPath);
+ // Obtains the existing excluded node.
+ Set<String> alreadyExistExcludedNodes =
+ parseExcludedNodesFile(hadoopFileSystem.open(hadoopPath));
+ // Writes to the new excluded node.
+ alreadyExistExcludedNodes.removeAll(excludedNodes);
+ writeExcludedNodes2File(Lists.newArrayList(alreadyExistExcludedNodes));
+ return true;
+ }
+
@Override
public void add(ServerNode node) {
ServerNode pre = servers.get(node.getId());
@@ -383,6 +398,19 @@ public class SimpleClusterManager implements
ClusterManager {
}
}
+ @Override
+ public boolean removeExcludedNodesFromFile(List<String> excludedNodeIds) {
+ try {
+ if (removeExcludedNodesFile(excludedNodeIds)) {
+ excludedNodes.removeAll(excludedNodeIds);
+ return true;
+ }
+ } catch (IOException e) {
+ LOG.warn("Because {}, failed to add blacklist.", e.getMessage());
+ }
+ return false;
+ }
+
@VisibleForTesting
public void clear() {
servers.clear();
diff --git
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ServerResource.java
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ServerResource.java
index 8db72a19e..ade673112 100644
---
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ServerResource.java
+++
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ServerResource.java
@@ -233,6 +233,17 @@ public class ServerResource extends BaseResource {
return Response.fail("fail");
}
+ @POST
+ @Path("/removeExcludeNodes")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response<String> handleDeleteExcludeNodesRequest(Map<String,
List<String>> excludeNodes) {
+ ClusterManager clusterManager = getClusterManager();
+ if
(clusterManager.removeExcludedNodesFromFile(excludeNodes.get("excludeNodes"))) {
+ return Response.success("success");
+ }
+ return Response.fail("fail");
+ }
+
private ClusterManager getClusterManager() {
return (ClusterManager)
servletContext.getAttribute(ClusterManager.class.getCanonicalName());
}
diff --git a/dashboard/src/main/webapp/src/api/api.js
b/dashboard/src/main/webapp/src/api/api.js
index eb4755183..c0a827f44 100644
--- a/dashboard/src/main/webapp/src/api/api.js
+++ b/dashboard/src/main/webapp/src/api/api.js
@@ -94,6 +94,11 @@ export function addShuffleExcludeNodes(params, headers) {
return http.post('/server/addExcludeNodes', params, headers, 0)
}
+// Create an interface for remove blacklist
+export function removeShuffleExcludeNodes(params, headers) {
+ return http.post('/server/removeExcludeNodes', params, headers, 0)
+}
+
// Total number of interfaces for new App
export function getAppTotal(params, headers) {
return http.get('/app/total', params, headers, 0)
diff --git a/dashboard/src/main/webapp/src/main.js
b/dashboard/src/main/webapp/src/main.js
index 3a3cbdace..2d2e95d73 100644
--- a/dashboard/src/main/webapp/src/main.js
+++ b/dashboard/src/main/webapp/src/main.js
@@ -22,7 +22,7 @@ import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import router from '@/router'
-// import '@/mock' // With this annotation turned on, you can use the
front-end mock data without requesting a background interface.
+// import '@/mock' // With this annotation turned on, you can use the
front-end mock data without requesting a background interface.
const app = createApp(App)
const pinia = createPinia()
diff --git
a/dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
b/dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
index c1b5fa0af..e12768fdd 100644
--- a/dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
+++ b/dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
@@ -17,19 +17,32 @@
<template>
<div>
- <div style="text-align: right">
- <el-button type="primary" @click="dialogFormVisible = true"> Add Node
</el-button>
+ <div class="button-wrapper">
+ <el-button type="success" @click="dialogFormVisible = true"> Add Node
</el-button>
+ <el-button type="danger" @click="handleDeleteNode">Delete({{
selectItemNum }})</el-button>
</div>
+ <el-divider />
<div>
<el-table
- :data="pageData.tableData"
- height="550"
- style="width: 100%"
+ :data="filteredTableData"
+ :row-key="rowKey"
:default-sort="sortColumn"
@sort-change="sortChangeEvent"
+ @selection-change="handlerSelectionChange"
+ class="table-wapper"
+ ref="table"
+ stripe
>
+ <el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ExcludeNodeId" min-width="180"
:sortable="true" />
+ <el-table-column align="right">
+ <template #header>
+ <el-input v-model="searchKeyword" size="small" placeholder="Type
to search" />
+ </template>
+ </el-table-column>
</el-table>
+ </div>
+ <div>
<el-dialog
v-model="dialogFormVisible"
title="Please enter the server id list to be excluded:"
@@ -49,7 +62,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogFormVisible = false">Cancel</el-button>
- <el-button type="primary" @click="confirmAddHandler"> Confirm
</el-button>
+ <el-button type="primary" @click="handleConfirmAddHandler">
Confirm </el-button>
</div>
</template>
</el-dialog>
@@ -57,20 +70,18 @@
</div>
</template>
<script>
-import { onMounted, reactive, ref, inject } from 'vue'
-import { addShuffleExcludeNodes, getShuffleExcludeNodes } from '@/api/api'
+import { onMounted, reactive, ref, inject, computed } from 'vue'
+import {
+ addShuffleExcludeNodes,
+ getShuffleExcludeNodes,
+ removeShuffleExcludeNodes
+} from '@/api/api'
import { useCurrentServerStore } from '@/store/useCurrentServerStore'
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
export default {
setup() {
- const pageData = reactive({
- tableData: [
- {
- id: ''
- }
- ]
- })
+ const pageData = reactive({ tableData: [] })
const currentServerStore = useCurrentServerStore()
const dialogFormVisible = ref(false)
@@ -87,25 +98,6 @@ export default {
pageData.tableData = res.data.data
}
- async function addShuffleExcludeNodesPage() {
- try {
- const excludeNodes = textarea.value.split('\n').map((item) =>
item.trim())
- const excludeNodesObj = { excludeNodes }
- const res = await addShuffleExcludeNodes(excludeNodesObj)
- if (res.status >= 200 && res.status < 300) {
- if (res.data.data === 'success') {
- ElMessage.success('Add successfully.')
- } else {
- ElMessage.error('Add failed.')
- }
- } else {
- ElMessage.error('Failed to add due to server bad.')
- }
- } catch (err) {
- ElMessage.error('Failed to add due to network exception.')
- }
- }
-
// The system obtains data from global variables and requests the
interface to obtain new data after data changes.
currentServerStore.$subscribe((mutable, state) => {
if (state.currentServer) {
@@ -120,6 +112,9 @@ export default {
}
})
+ /**
+ * The following describes how to handle sort events.
+ */
const sortColumn = reactive({})
const sortChangeEvent = (sortInfo) => {
for (const sortColumnKey in sortColumn) {
@@ -127,7 +122,11 @@ export default {
}
sortColumn[sortInfo.prop] = sortInfo.order
}
- const confirmAddHandler = () => {
+
+ /**
+ * The following describes how to handle add events.
+ */
+ function handleConfirmAddHandler() {
dialogFormVisible.value = false
addShuffleExcludeNodesPage()
// Refreshing the number of blacklists.
@@ -136,14 +135,115 @@ export default {
getShuffleExcludeNodesPage()
}
+ async function addShuffleExcludeNodesPage() {
+ try {
+ const excludeNodes = textarea.value.split('\n').map((item) =>
item.trim())
+ const excludeNodesObj = { excludeNodes }
+ const res = await addShuffleExcludeNodes(excludeNodesObj)
+ if (res.status >= 200 && res.status < 300) {
+ if (res.data.data === 'success') {
+ ElMessage.success('Add successfully.')
+ } else {
+ ElMessage.error('Add failed.')
+ }
+ } else {
+ ElMessage.error('Failed to add due to server bad.')
+ }
+ } catch (err) {
+ ElMessage.error('Failed to add due to network exception.')
+ }
+ }
+
+ /**
+ * The following describes how to handle blacklist deletion events.
+ */
+ const selectItemNum = ref(0)
+ const rowKey = 'id'
+ const selectedRows = ref([])
+ function handlerSelectionChange(selection) {
+ selectedRows.value = selection
+ selectItemNum.value = selectedRows.value.length
+ }
+ function handleDeleteNode() {
+ ElMessageBox.confirm('Are you sure about removing these nodes?',
'Warning', {
+ confirmButtonText: 'OK',
+ cancelButtonText: 'Cancel',
+ type: 'warning'
+ })
+ .then(() => {
+ if (selectedRows.value.length === 0) {
+ ElMessage({
+ type: 'info',
+ message: 'No node is selected, Nothing!'
+ })
+ } else {
+ const selectedIds = selectedRows.value.map((row) => row[rowKey])
+ // pageData.tableData = pageData.tableData.filter(row =>
!selectedIds.includes(row[rowKey]));
+ deleteShuffleExcludedNodes(selectedIds)
+ // Refreshing the number of blacklists.
+ updateTotalPage()
+ // Refreshing the Blacklist list.
+ getShuffleExcludeNodesPage()
+ ElMessage({
+ type: 'success',
+ message: 'Delete completed'
+ })
+ }
+ })
+ .catch(() => {
+ ElMessage({
+ type: 'info',
+ message: 'Delete canceled'
+ })
+ })
+ }
+
+ async function deleteShuffleExcludedNodes(excludeNodes) {
+ try {
+ const excludeNodesObj = { excludeNodes }
+ const res = await removeShuffleExcludeNodes(excludeNodesObj)
+ if (res.status >= 200 && res.status < 300) {
+ if (res.data.data === 'success') {
+ ElMessage.success('Add successfully.')
+ } else {
+ ElMessage.error('Add failed.')
+ }
+ } else {
+ ElMessage.error('Failed to add due to server bad.')
+ }
+ } catch (err) {
+ ElMessage.error('Failed to add due to network exception.')
+ }
+ }
+
+ /**
+ * The following describes how to handle blacklist select events.
+ */
+ const searchKeyword = ref('')
+ const filteredTableData = computed(() => {
+ const keyword = searchKeyword.value.trim()
+ if (!keyword) {
+ return pageData.tableData
+ } else {
+ return pageData.tableData.filter((row) => {
+ return row.id.includes(keyword)
+ })
+ }
+ })
return {
pageData,
sortColumn,
+ selectItemNum,
sortChangeEvent,
- confirmAddHandler,
+ handleConfirmAddHandler,
+ handleDeleteNode,
+ handlerSelectionChange,
dialogFormVisible,
formLabelWidth,
- textarea
+ textarea,
+ rowKey,
+ searchKeyword,
+ filteredTableData
}
}
}
@@ -156,4 +256,9 @@ export default {
.dialog-wrapper {
width: 50%;
}
+.table-wapper {
+ height: 550px;
+ width: 100%;
+ text-align: right;
+}
</style>