mirror of
https://github.com/louislam/dockge.git
synced 2026-05-21 14:02:17 +00:00
Improved stack list ui when using agents (#800)
This commit is contained in:
committed by
GitHub
parent
72a941712d
commit
7e324d9015
@@ -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,34 +29,36 @@
|
|||||||
|
|
||||||
<!-- 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]) }}
|
||||||
</span>
|
</span>
|
||||||
</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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -331,7 +358,7 @@ export default {
|
|||||||
pauseSelected() {
|
pauseSelected() {
|
||||||
Object.keys(this.selectedStacks)
|
Object.keys(this.selectedStacks)
|
||||||
.filter(id => this.$root.stackList[id].active)
|
.filter(id => this.$root.stackList[id].active)
|
||||||
.forEach(id => this.$root.getSocket().emit("pauseStack", id, () => {}));
|
.forEach(id => this.$root.getSocket().emit("pauseStack", id, () => { }));
|
||||||
|
|
||||||
this.cancelSelectMode();
|
this.cancelSelectMode();
|
||||||
},
|
},
|
||||||
@@ -342,7 +369,7 @@ export default {
|
|||||||
resumeSelected() {
|
resumeSelected() {
|
||||||
Object.keys(this.selectedStacks)
|
Object.keys(this.selectedStacks)
|
||||||
.filter(id => !this.$root.stackList[id].active)
|
.filter(id => !this.$root.stackList[id].active)
|
||||||
.forEach(id => this.$root.getSocket().emit("resumeStack", id, () => {}));
|
.forEach(id => this.$root.getSocket().emit("resumeStack", id, () => { }));
|
||||||
|
|
||||||
this.cancelSelectMode();
|
this.cancelSelectMode();
|
||||||
},
|
},
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user