Skip to content

Commit

Permalink
[apache#1401] feat(dashboard): Support removing excluded servers (apa…
Browse files Browse the repository at this point in the history
…che#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.

![image](https://github.com/user-attachments/assets/3e737f69-9a23-4e20-ba31-7d05c7300fdd)
![image](https://github.com/user-attachments/assets/7e3ec007-ff1f-4a52-88ac-e23f17dd56e9)

### Why are the changes needed?

Fix: apache#1401 

### Does this PR introduce _any_ user-facing change?

Yes.

### How was this patch tested?

UT.
  • Loading branch information
yl09099 authored Aug 2, 2024
1 parent f5261d3 commit 77e8612
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,6 @@ public interface ClusterManager extends Closeable {

/** Add blacklist. */
boolean addExcludedNodes(List<String> excludedNodeIds);

boolean removeExcludedNodesFromFile(List<String> excludedNodeIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,21 @@ private synchronized boolean putInExcludedNodesFile(List<String> excludedNodes)
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());
Expand Down Expand Up @@ -383,6 +398,19 @@ public boolean addExcludedNodes(List<String> excludedNodeIds) {
}
}

@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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,17 @@ public Response<String> handleAddExcludedNodesRequest(Map<String, List<String>>
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());
}
Expand Down
5 changes: 5 additions & 0 deletions dashboard/src/main/webapp/src/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/main/webapp/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
181 changes: 143 additions & 38 deletions dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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:"
Expand All @@ -49,28 +62,26 @@
<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>
</div>
</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)
Expand All @@ -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) {
Expand All @@ -120,14 +112,21 @@ export default {
}
})
/**
* The following describes how to handle sort events.
*/
const sortColumn = reactive({})
const sortChangeEvent = (sortInfo) => {
for (const sortColumnKey in sortColumn) {
delete sortColumn[sortColumnKey]
}
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.
Expand All @@ -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
}
}
}
Expand All @@ -156,4 +256,9 @@ export default {
.dialog-wrapper {
width: 50%;
}
.table-wapper {
height: 550px;
width: 100%;
text-align: right;
}
</style>

0 comments on commit 77e8612

Please sign in to comment.