Improved stack list ui when using agents (#800)

This commit is contained in:
Matthew McConnell
2026-04-12 02:27:16 +07:00
committed by GitHub
parent 72a941712d
commit 7e324d9015
3 changed files with 68 additions and 27 deletions

View File

@@ -3,7 +3,8 @@
<div class="list-header"> <div class="list-header">
<div class="header-top"> <div class="header-top">
<!-- TODO --> <!-- TODO -->
<button v-if="false" class="btn btn-outline-normal ms-2" :class="{ 'active': selectMode }" type="button" @click="selectMode = !selectMode"> <button v-if="false" class="btn btn-outline-normal ms-2" :class="{ 'active': selectMode }" type="button"
@click="selectMode = !selectMode">
{{ $t("Select") }} {{ $t("Select") }}
</button> </button>
@@ -28,14 +29,12 @@
<!-- TODO: Selection Controls --> <!-- TODO: Selection Controls -->
<div v-if="selectMode && false" class="selection-controls px-2 pt-2"> <div v-if="selectMode && false" class="selection-controls px-2 pt-2">
<input <input v-model="selectAll" class="form-check-input select-input" type="checkbox" />
v-model="selectAll"
class="form-check-input select-input"
type="checkbox"
/>
<button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{ $t("Pause") }}</button> <button class="btn-outline-normal" @click="pauseDialog"><font-awesome-icon icon="pause" size="sm" /> {{
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" /> {{ $t("Resume") }}</button> $t("Pause") }}</button>
<button class="btn-outline-normal" @click="resumeSelected"><font-awesome-icon icon="play" size="sm" />
{{ $t("Resume") }}</button>
<span v-if="selectedStackCount > 0"> <span v-if="selectedStackCount > 0">
{{ $t("selectedStackCount", [selectedStackCount]) }} {{ $t("selectedStackCount", [selectedStackCount]) }}
@@ -43,19 +42,23 @@
</div> </div>
</div> </div>
<div ref="stackList" class="stack-list" :class="{ scrollbar: scrollbar }" :style="stackListStyle"> <div ref="stackList" class="stack-list" :class="{ scrollbar: scrollbar }" :style="stackListStyle">
<div v-if="Object.keys(sortedStackList).length === 0" class="text-center mt-3"> <div v-if="agentStackList[0] && agentStackList[0].stacks.length === 0" class="text-center mt-3">
<router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link> <router-link to="/compose">{{ $t("addFirstStackMsg") }}</router-link>
</div> </div>
<div class="stack-list-inner" v-for="(agent, index) in agentStackList" :key="index">
<StackListItem <div v-if="$root.agentCount > 1" class="p-2 agent-select"
v-for="(item, index) in sortedStackList" @click="closedAgents.set(agent.endpoint, !closedAgents.get(agent.endpoint))">
:key="index" <span class="me-1">
:stack="item" <font-awesome-icon v-show="closedAgents.get(agent.endpoint)" icon="chevron-circle-right" />
:isSelectMode="selectMode" <font-awesome-icon v-show="!closedAgents.get(agent.endpoint)" icon="chevron-circle-down" />
:isSelected="isSelected" </span>
:select="select" <span v-if="agent.endpoint === 'current'">{{ $t("currentEndpoint") }}</span>
:deselect="deselect" <span v-else>{{ agent.endpoint }}</span>
/> </div>
<StackListItem v-show="$root.agentCount === 1 || !closedAgents.get(agent.endpoint)"
v-for="(item, index) in agent.stacks" :key="index" :stack="item" :isSelectMode="selectMode"
:isSelected="isSelected" :select="select" :deselect="deselect" />
</div>
</div> </div>
</div> </div>
@@ -92,7 +95,8 @@ export default {
status: null, status: null,
active: null, active: null,
tags: null, tags: null,
} },
closedAgents: new Map(),
}; };
}, },
computed: { computed: {
@@ -119,7 +123,7 @@ export default {
* Returns a sorted list of stacks based on the applied filters and search text. * Returns a sorted list of stacks based on the applied filters and search text.
* @returns {Array} The sorted list of stacks. * @returns {Array} The sorted list of stacks.
*/ */
sortedStackList() { agentStackList() {
let result = Object.values(this.$root.completeStackList); let result = Object.values(this.$root.completeStackList);
result = result.filter(stack => { result = result.filter(stack => {
@@ -187,6 +191,29 @@ export default {
return m1.name.localeCompare(m2.name); return m1.name.localeCompare(m2.name);
}); });
// Group stacks by endpoint, sorting them so the local endpoint is first
// and the rest are sorted alphabetically
result = [
...result.reduce((acc, stack) => {
const endpoint = stack.endpoint || 'current';
if (!acc.has(endpoint)) {
acc.set(endpoint, []);
}
acc.get(endpoint).push(stack);
return acc;
}, new Map()).entries()
].map(([endpoint, stacks]) => ({
endpoint,
stacks
})).sort((a, b) => {
if (a.endpoint === 'current' && b.endpoint !== 'current') {
return -1;
} else if (a.endpoint !== 'current' && b.endpoint === 'current') {
return 1;
}
return a.endpoint.localeCompare(b.endpoint);
});
return result; return result;
}, },
@@ -221,7 +248,7 @@ export default {
}, },
watch: { watch: {
searchText() { searchText() {
for (let stack of this.sortedStackList) { for (let stack of this.agentStackList) {
if (!this.selectedStacks[stack.id]) { if (!this.selectedStacks[stack.id]) {
if (this.selectAll) { if (this.selectAll) {
this.disableSelectAllWatcher = true; this.disableSelectAllWatcher = true;
@@ -236,7 +263,7 @@ export default {
this.selectedStacks = {}; this.selectedStacks = {};
if (this.selectAll) { if (this.selectAll) {
this.sortedStackList.forEach((item) => { this.agentStackList.forEach((item) => {
this.selectedStacks[item.id] = true; this.selectedStacks[item.id] = true;
}); });
} }
@@ -444,4 +471,15 @@ export default {
gap: 10px; gap: 10px;
} }
.agent-select {
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: $dark-font-color3;
padding-left: 10px;
padding-right: 10px;
display: flex;
align-items: center;
user-select: none;
}
</style> </style>

View File

@@ -3,7 +3,6 @@
<Uptime :stack="stack" :fixed-width="true" class="me-2" /> <Uptime :stack="stack" :fixed-width="true" class="me-2" />
<div class="title"> <div class="title">
<span>{{ stackName }}</span> <span>{{ stackName }}</span>
<div v-if="$root.agentCount > 1" class="endpoint">{{ endpointDisplay }}</div>
</div> </div>
</router-link> </router-link>
</template> </template>

View File

@@ -54,6 +54,8 @@ import {
faTerminal, faWarehouse, faHome, faRocket, faTerminal, faWarehouse, faHome, faRocket,
faRotate, faRotate,
faCloudArrowDown, faArrowsRotate, faCloudArrowDown, faArrowsRotate,
faChevronCircleRight,
faChevronCircleDown,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
library.add( library.add(
@@ -109,6 +111,8 @@ library.add(
faRotate, faRotate,
faCloudArrowDown, faCloudArrowDown,
faArrowsRotate, faArrowsRotate,
faChevronCircleRight,
faChevronCircleDown,
); );
export { FontAwesomeIcon }; export { FontAwesomeIcon };