mirror of https://github.com/halo-dev/halo
refactor: optimize auth provider sorting with drag-and-drop support (#5914)
#### What type of PR is this? /kind feature /area core /area ui /milestone 2.16.x #### What this PR does / why we need it: 优化认证方式的排序并支持拖动 #### Which issue(s) this PR fixes: Fixes #5813 #### Does this PR introduce a user-facing change? ```release-note 优化认证方式的排序并支持拖动 ```pull/5953/head^2
parent
94d625fbb0
commit
c22b4e9ef4
|
@ -13221,6 +13221,10 @@
|
||||||
"logo": {
|
"logo": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
"settingRef": {
|
"settingRef": {
|
||||||
"$ref": "#/components/schemas/SettingRef"
|
"$ref": "#/components/schemas/SettingRef"
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,6 +52,8 @@ public class AuthProvider extends AbstractExtension {
|
||||||
|
|
||||||
private String unbindUrl;
|
private String unbindUrl;
|
||||||
|
|
||||||
|
private int priority;
|
||||||
|
|
||||||
@Schema(requiredMode = NOT_REQUIRED)
|
@Schema(requiredMode = NOT_REQUIRED)
|
||||||
private SettingRef settingRef;
|
private SettingRef settingRef;
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ public class AuthProviderServiceImpl implements AuthProviderService {
|
||||||
public Mono<List<ListedAuthProvider>> listAll() {
|
public Mono<List<ListedAuthProvider>> listAll() {
|
||||||
return client.list(AuthProvider.class, provider ->
|
return client.list(AuthProvider.class, provider ->
|
||||||
provider.getMetadata().getDeletionTimestamp() == null,
|
provider.getMetadata().getDeletionTimestamp() == null,
|
||||||
Comparator.comparing(item -> item.getMetadata().getCreationTimestamp())
|
defaultComparator()
|
||||||
)
|
)
|
||||||
.map(this::convertTo)
|
.map(this::convertTo)
|
||||||
.collectList()
|
.collectList()
|
||||||
|
@ -105,6 +105,12 @@ public class AuthProviderServiceImpl implements AuthProviderService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Comparator<AuthProvider> defaultComparator() {
|
||||||
|
return Comparator.comparing((AuthProvider item) -> item.getSpec().getPriority())
|
||||||
|
.thenComparing(item -> item.getMetadata().getName())
|
||||||
|
.thenComparing(item -> item.getMetadata().getCreationTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<ConfigMap> updateAuthProviderEnabled(Consumer<Set<String>> consumer) {
|
private Mono<ConfigMap> updateAuthProviderEnabled(Consumer<Set<String>> consumer) {
|
||||||
return client.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG)
|
return client.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG)
|
||||||
.switchIfEmpty(Mono.defer(() -> {
|
.switchIfEmpty(Mono.defer(() -> {
|
||||||
|
|
|
@ -7,22 +7,22 @@ import {
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { ListedAuthProvider } from "@halo-dev/api-client";
|
import type { AuthProvider, ListedAuthProvider } from "@halo-dev/api-client";
|
||||||
import AuthProviderListItem from "./components/AuthProviderListItem.vue";
|
import AuthProviderListItem from "./components/AuthProviderListItem.vue";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
import { VueDraggable } from "vue-draggable-plus";
|
||||||
|
|
||||||
const {
|
const authProviders = ref<ListedAuthProvider[]>([]);
|
||||||
data: authProviders,
|
|
||||||
isLoading,
|
const { isLoading, refetch } = useQuery<ListedAuthProvider[]>({
|
||||||
refetch,
|
|
||||||
} = useQuery<ListedAuthProvider[]>({
|
|
||||||
queryKey: ["auth-providers"],
|
queryKey: ["auth-providers"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiClient.authProvider.listAuthProviders();
|
const { data } = await apiClient.authProvider.listAuthProviders();
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
|
authProviders.value = data;
|
||||||
fuse = new Fuse(data, {
|
fuse = new Fuse(data, {
|
||||||
keys: ["name", "displayName"],
|
keys: ["name", "displayName"],
|
||||||
useExtendedSearch: true,
|
useExtendedSearch: true,
|
||||||
|
@ -41,6 +41,44 @@ const searchResults = computed(() => {
|
||||||
|
|
||||||
return fuse?.search(keyword.value).map((item) => item.item);
|
return fuse?.search(keyword.value).map((item) => item.item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Drag and drop
|
||||||
|
const updating = ref(false);
|
||||||
|
|
||||||
|
async function onSortUpdate() {
|
||||||
|
try {
|
||||||
|
updating.value = true;
|
||||||
|
|
||||||
|
const { data: rawAuthProviders } =
|
||||||
|
await apiClient.extension.authProvider.listauthHaloRunV1alpha1AuthProvider();
|
||||||
|
|
||||||
|
const authProviderNames = authProviders.value.map((item) => item.name);
|
||||||
|
|
||||||
|
const sortedAuthProviders = authProviderNames
|
||||||
|
.map((name) => {
|
||||||
|
const authProvider = rawAuthProviders.items.find(
|
||||||
|
(item) => item.metadata.name === name
|
||||||
|
);
|
||||||
|
if (authProvider) {
|
||||||
|
authProvider.spec.priority = authProviderNames.indexOf(name);
|
||||||
|
}
|
||||||
|
return authProvider;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as AuthProvider[];
|
||||||
|
|
||||||
|
for (const authProvider of sortedAuthProviders) {
|
||||||
|
await apiClient.extension.authProvider.updateauthHaloRunV1alpha1AuthProvider(
|
||||||
|
{
|
||||||
|
name: authProvider.metadata.name,
|
||||||
|
authProvider: authProvider,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await refetch();
|
||||||
|
updating.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -69,9 +107,18 @@ const searchResults = computed(() => {
|
||||||
</template>
|
</template>
|
||||||
<VLoading v-if="isLoading" />
|
<VLoading v-if="isLoading" />
|
||||||
<Transition v-else appear name="fade">
|
<Transition v-else appear name="fade">
|
||||||
<ul
|
<VueDraggable
|
||||||
|
v-model="authProviders"
|
||||||
|
ghost-class="opacity-50"
|
||||||
|
handle=".drag-element"
|
||||||
class="box-border h-full w-full divide-y divide-gray-100"
|
class="box-border h-full w-full divide-y divide-gray-100"
|
||||||
|
:class="{
|
||||||
|
'cursor-progress opacity-60': updating,
|
||||||
|
}"
|
||||||
role="list"
|
role="list"
|
||||||
|
tag="ul"
|
||||||
|
:disabled="updating"
|
||||||
|
@update="onSortUpdate"
|
||||||
>
|
>
|
||||||
<li v-for="(authProvider, index) in searchResults" :key="index">
|
<li v-for="(authProvider, index) in searchResults" :key="index">
|
||||||
<AuthProviderListItem
|
<AuthProviderListItem
|
||||||
|
@ -79,7 +126,7 @@ const searchResults = computed(() => {
|
||||||
@reload="refetch"
|
@reload="refetch"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</VueDraggable>
|
||||||
</Transition>
|
</Transition>
|
||||||
</VCard>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { apiClient } from "@/utils/api-client";
|
||||||
import type { ListedAuthProvider } from "@halo-dev/api-client";
|
import type { ListedAuthProvider } from "@halo-dev/api-client";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
IconList,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
Toast,
|
Toast,
|
||||||
VAvatar,
|
VAvatar,
|
||||||
|
@ -55,6 +56,13 @@ const handleChangeStatus = async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VEntity>
|
<VEntity>
|
||||||
|
<template #prepend>
|
||||||
|
<div
|
||||||
|
class="drag-element absolute inset-y-0 left-0 hidden w-3.5 cursor-move items-center bg-gray-100 transition-all hover:bg-gray-200 group-hover:flex"
|
||||||
|
>
|
||||||
|
<IconList class="h-3.5 w-3.5" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template #start>
|
<template #start>
|
||||||
<VEntityField>
|
<VEntityField>
|
||||||
<template #description>
|
<template #description>
|
||||||
|
|
|
@ -97,6 +97,7 @@
|
||||||
"transliteration": "^2.3.5",
|
"transliteration": "^2.3.5",
|
||||||
"vue": "^3.4.19",
|
"vue": "^3.4.19",
|
||||||
"vue-demi": "^0.14.7",
|
"vue-demi": "^0.14.7",
|
||||||
|
"vue-draggable-plus": "^0.4.1",
|
||||||
"vue-grid-layout": "3.0.0-beta1",
|
"vue-grid-layout": "3.0.0-beta1",
|
||||||
"vue-i18n": "^9.9.1",
|
"vue-i18n": "^9.9.1",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "^4.2.5",
|
||||||
|
|
|
@ -68,6 +68,12 @@ export interface AuthProviderSpec {
|
||||||
* @memberof AuthProviderSpec
|
* @memberof AuthProviderSpec
|
||||||
*/
|
*/
|
||||||
'logo'?: string;
|
'logo'?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AuthProviderSpec
|
||||||
|
*/
|
||||||
|
'priority'?: number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {SettingRef}
|
* @type {SettingRef}
|
||||||
|
|
|
@ -185,6 +185,9 @@ importers:
|
||||||
vue-demi:
|
vue-demi:
|
||||||
specifier: ^0.14.7
|
specifier: ^0.14.7
|
||||||
version: 0.14.7(vue@3.4.19)
|
version: 0.14.7(vue@3.4.19)
|
||||||
|
vue-draggable-plus:
|
||||||
|
specifier: ^0.4.1
|
||||||
|
version: 0.4.1(@types/sortablejs@1.15.8)
|
||||||
vue-grid-layout:
|
vue-grid-layout:
|
||||||
specifier: 3.0.0-beta1
|
specifier: 3.0.0-beta1
|
||||||
version: 3.0.0-beta1(@interactjs/core@1.10.17)(@interactjs/utils@1.10.17)
|
version: 3.0.0-beta1(@interactjs/core@1.10.17)(@interactjs/utils@1.10.17)
|
||||||
|
@ -8101,6 +8104,10 @@ packages:
|
||||||
resolution: {integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==}
|
resolution: {integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/sortablejs@1.15.8:
|
||||||
|
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/tough-cookie@4.0.2:
|
/@types/tough-cookie@4.0.2:
|
||||||
resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==}
|
resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -20089,6 +20096,18 @@ packages:
|
||||||
vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.4.19)
|
vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.4.19)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/vue-draggable-plus@0.4.1(@types/sortablejs@1.15.8):
|
||||||
|
resolution: {integrity: sha512-KNi+c482OQUZTZ2kXIGc41fEwknkNF+LlngjBr5TVtBLNvpX2dmwRJJ3J7dy5dGcijXb7V1j+mhqce4iHOoi6Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/sortablejs': ^1.15.0
|
||||||
|
'@vue/composition-api': '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vue/composition-api':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/sortablejs': 1.15.8
|
||||||
|
dev: false
|
||||||
|
|
||||||
/vue-eslint-parser@9.3.0(eslint@8.43.0):
|
/vue-eslint-parser@9.3.0(eslint@8.43.0):
|
||||||
resolution: {integrity: sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ==}
|
resolution: {integrity: sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ==}
|
||||||
engines: {node: ^14.17.0 || >=16.0.0}
|
engines: {node: ^14.17.0 || >=16.0.0}
|
||||||
|
|
Loading…
Reference in New Issue