7 Commits

Author SHA1 Message Date
guqing
40f66ff665 chore: remove deprecated plugin wrapper (#161)
### What this PR does?
移除对已过时的 PluginWrapper 的引用,Halo 2.18.0 版本后将不在支持从 BasePlugin 中获取 PluginWrapper ,也不再支持依赖注入 PluginWrapper,使用 PluginContext 代替

see also https://github.com/halo-dev/halo/pull/6243 for more details

```release-note
None
```
2024-07-02 15:29:25 +00:00
longjuan
cb968de154 fix: apply natural ordering to policy and attachment listings (#160)
```release-note
None
```
传递unsorted以支持Halo 2.17版本
2024-07-02 15:27:25 +00:00
longjuan
bdd62cbe12 faet: Add file prefix search function in s3link (#153)
```release-note
在 S3关联 中支持文件名前缀搜索
```
fixes https://github.com/halo-dev/plugin-s3/issues/142
2024-06-16 12:30:45 +00:00
longjuan
2a26171fc4 Fix: Correct policy selector styling issue (#151)
```release-note
修复 S3 关联中策略选择器太短的问题
```
fixes #141
2024-05-14 02:08:33 +00:00
longjuan
6b62ce7aa4 Hide the permission of the verification interface (#147)
fixes https://github.com/halo-dev/plugin-s3/issues/146
```release-note
在角色创建中隐藏配置校验接口的权限
```
2024-05-06 10:34:54 +00:00
Ryan Wang
c60e31a033 chore: update plugin.yaml file (#138)
完善插件定义文件。

1. 将 homepage 字段指向 Halo 应用市场的地址。
2. 新增 repo 字段指向源码仓库地址。
3. 新增 issues 字段指向 GitHub issues 地址。

```release-note
None
```
2024-05-05 08:39:33 +00:00
Chenhe
7a9b0de0c6 improve usage tips in settings page (#140)
使用占位组件在设置页面展示信息并不恰当,并且此项目作为存储插件的 Example 工程易误导其他开发者。

调整为利用 FormKit `el` 特性实现。

问题背景:https://github.com/halo-dev/halo/issues/5802

**Preview - old**
<img width="373" alt="image" src="https://github.com/halo-dev/plugin-s3/assets/10266066/ac4aaede-6377-459f-a866-9bfe132906af">

**Preview - new**
<img width="373" alt="image" src="https://github.com/halo-dev/plugin-s3/assets/10266066/f884417c-045a-4396-84be-633d18ae36c9">

```release-note
优化调整提示的提示
```
2024-04-28 11:54:48 +00:00
10 changed files with 76 additions and 36 deletions

View File

@@ -10,6 +10,7 @@ export function getApisS3OsHaloRunV1Alpha1ObjectsByPolicyName(params: GetApisS3O
continuationObject: params.continuationObject,
pageSize: params.pageSize,
unlinked: params.unlinked,
filePrefix: params.filePrefix,
};
return request.get<DeepRequired<S3ListResult>>(`/apis/s3os.halo.run/v1alpha1/objects/${params.policyName}`, {
params: paramsInput,
@@ -22,4 +23,5 @@ interface GetApisS3OsHaloRunV1Alpha1ObjectsByPolicyNameParams {
continuationObject?: any;
pageSize: any;
unlinked?: any;
filePrefix?: any;
}

View File

@@ -18,7 +18,9 @@ request.interceptors.response.use(
return Promise.reject(error);
}
const { status } = errorResponse;
if (status !== 200) {
if (status === 400) {
Toast.error(errorResponse.data.detail);
} else if (status !== 200) {
Toast.error("status: " + status);
}
return Promise.reject(error);

View File

@@ -14,6 +14,7 @@ import {
VTag,
} from "@halo-dev/components";
import CarbonFolderDetailsReference from "~icons/carbon/folder-details-reference";
import IconErrorWarning from "~icons/ri/error-warning-line";
import {computed, onMounted, ref, watch} from "vue";
import {
getApisS3OsHaloRunV1Alpha1ObjectsByPolicyName,
@@ -27,10 +28,14 @@ const policyName = ref<string>("");
const page = ref(1);
const size = ref(50);
const policyOptions = ref<{ label: string; value: string; attrs: any }[]>([{
label: "请选择策略",
label: "请选择存储策略",
value: "",
attrs: {disabled: true}
}]);
// update when fetch first page
const filePrefix = ref<string>("");
// update when user input
const filePrefixBind = ref<string>("");
const s3Objects = ref<S3ListResult>({
objects: [],
hasMore: false,
@@ -58,15 +63,15 @@ const selectedLinkedStatusItem = ref<boolean | undefined>(linkedStatusItems[0].v
const emptyTips = computed(() => {
if (isFetchingPolicies.value) {
return "正在加载策略";
return "正在加载存储策略";
} else {
if (policyOptions.value.length <= 1) {
return "没有可用的策略请前往【附件】添加S3策略";
return "没有可用的存储策略请前往【附件】添加S3存储策略";
} else {
if (!policyName.value) {
return "请在左上方选择策略";
return "请在左上方选择存储策略";
} else {
return "该策略的 桶/文件夹 下没有文件";
return "该存储策略的 桶/文件夹 下没有文件";
}
}
}
@@ -92,7 +97,7 @@ const fetchPolicies = async () => {
const policiesData = await getApisS3OsHaloRunV1Alpha1PoliciesS3();
if (policiesData.status == 200) {
policyOptions.value = [{
label: "请选择策略",
label: "请选择存储策略",
value: "",
attrs: {disabled: true}
}];
@@ -139,6 +144,8 @@ const clearTokenAndObject = () => {
s3Objects.value.nextContinuationObject = "";
};
// filePrefix will not be updated from user input
// if you want to update filePrefix, please call `handleFirstPage`
const fetchObjects = async () => {
if (!policyName.value) {
return;
@@ -152,6 +159,7 @@ const fetchObjects = async () => {
continuationToken: s3Objects.value.currentToken,
continuationObject: s3Objects.value.currentContinuationObject,
unlinked: selectedLinkedStatusItem.value,
filePrefix: filePrefix.value
});
if (objectsData.status == 200) {
s3Objects.value = objectsData.data;
@@ -222,6 +230,7 @@ const handleFirstPage = () => {
isFetching.value = true;
page.value = 1;
clearTokenAndObject();
filePrefix.value = filePrefixBind.value;
fetchObjects();
};
@@ -255,18 +264,26 @@ const handleModalClose = () => {
<div class="flex w-full flex-1 items-center sm:w-auto">
<div
v-if="!selectedFiles.length"
class="flex items-center gap-2"
class="flex flex-wrap items-center gap-2"
>
策略:
<span class="whitespace-nowrap">存储策略:</span>
<FormKit
id="policyChoose"
outer-class="!p-0 w-48"
outer-class="!p-0"
style="min-width: 10rem;"
v-model="policyName"
name="policyName"
type="select"
:options="policyOptions"
@change="fetchObjects()"
@change="handleFirstPage"
></FormKit>
<icon-error-warning v-if="!policyName" class="text-red-500"/>
<SearchInput
v-model="filePrefixBind"
v-if="policyName"
placeholder="请输入文件名前缀搜索"
@update:modelValue="handleFirstPage"
></SearchInput>
</div>
<VSpace v-else>
<VButton type="primary" @click="handleLink">

View File

@@ -28,13 +28,13 @@ public class S3LinkController {
@RequestParam(name = "continuationToken", required = false) String continuationToken,
@RequestParam(name = "continuationObject", required = false) String continuationObject,
@RequestParam(name = "pageSize") Integer pageSize,
@RequestParam(name = "unlinked", required = false, defaultValue = "false")
Boolean unlinked) {
@RequestParam(name = "unlinked", required = false, defaultValue = "false") Boolean unlinked,
@RequestParam(name = "filePrefix", required = false) String filePrefix) {
if (unlinked) {
return s3LinkService.listObjectsUnlinked(policyName, continuationToken,
continuationObject, pageSize);
continuationObject, pageSize, filePrefix);
} else {
return s3LinkService.listObjects(policyName, continuationToken, pageSize);
return s3LinkService.listObjects(policyName, continuationToken, pageSize, filePrefix);
}
}

View File

@@ -10,10 +10,10 @@ public interface S3LinkService {
Flux<Policy> listS3Policies();
Mono<S3ListResult> listObjects(String policyName, String continuationToken,
Integer pageSize);
Integer pageSize, String filePrefix);
Mono<LinkResult> addAttachmentRecords(String policyName, Set<String> objectKeys);
Mono<S3ListResult> listObjectsUnlinked(String policyName, String continuationToken,
String continuationObject, Integer pageSize);
String continuationObject, Integer pageSize, String filePrefix);
}

View File

@@ -3,6 +3,7 @@ package run.halo.s3os;
import static run.halo.s3os.S3OsAttachmentHandler.OBJECT_KEY;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -14,6 +15,7 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
@@ -53,12 +55,12 @@ public class S3LinkServiceImpl implements S3LinkService {
@Override
public Flux<Policy> listS3Policies() {
return client.list(Policy.class, (policy) -> "s3os".equals(
policy.getSpec().getTemplateName()), null);
policy.getSpec().getTemplateName()), Comparator.naturalOrder());
}
@Override
public Mono<S3ListResult> listObjects(String policyName, String continuationToken,
Integer pageSize) {
Integer pageSize, String filePrefix) {
return client.fetch(Policy.class, policyName)
.flatMap((policy) -> {
var configMapName = policy.getSpec().getConfigMapName();
@@ -68,11 +70,11 @@ public class S3LinkServiceImpl implements S3LinkService {
var properties = handler.getProperties(configMap);
var finalLocation = FilePathUtils.getFilePathByPlaceholder(properties.getLocation());
return Mono.using(() -> handler.buildS3Client(properties),
// 执行 listObjects
(s3Client) -> Mono.fromCallable(
() -> s3Client.listObjectsV2(ListObjectsV2Request.builder()
.bucket(properties.getBucket())
.prefix(StringUtils.isNotEmpty(finalLocation)
? finalLocation + "/" : null)
.prefix(buildPrefix(finalLocation, filePrefix))
.delimiter("/")
.maxKeys(pageSize)
.continuationToken(StringUtils.isNotEmpty(continuationToken)
@@ -81,14 +83,16 @@ public class S3LinkServiceImpl implements S3LinkService {
S3Client::close)
.flatMap(listObjectsV2Response -> {
List<S3Object> contents = listObjectsV2Response.contents();
// 过滤掉目录并转换为ObjectVo
var objectVos = contents
.stream().map(S3ListResult.ObjectVo::fromS3Object)
.filter(objectVo -> !objectVo.getKey().endsWith("/"))
.collect(Collectors.toMap(S3ListResult.ObjectVo::getKey, o -> o));
// 获取已经关联的附件并标记
ListOptions listOptions = new ListOptions();
listOptions.setFieldSelector(
FieldSelector.of(QueryFactory.equal("spec.policyName", policyName)));
return client.listAll(Attachment.class, listOptions, null)
return client.listAll(Attachment.class, listOptions, Sort.unsorted())
.doOnNext(attachment -> {
S3ListResult.ObjectVo objectVo =
objectVos.get(attachment.getMetadata().getAnnotations()
@@ -130,7 +134,7 @@ public class S3LinkServiceImpl implements S3LinkService {
ListOptions listOptions = new ListOptions();
listOptions.setFieldSelector(
FieldSelector.of(QueryFactory.equal("spec.policyName", policyName)));
return client.listAll(Attachment.class, listOptions, null)
return client.listAll(Attachment.class, listOptions, Sort.unsorted())
.filter(attachment -> StringUtils.isNotBlank(
MetadataUtil.nullSafeAnnotations(attachment).get(S3OsAttachmentHandler.OBJECT_KEY))
&& objectKeys.contains(
@@ -163,7 +167,7 @@ public class S3LinkServiceImpl implements S3LinkService {
@Override
public Mono<S3ListResult> listObjectsUnlinked(String policyName, String continuationToken,
String continuationObject, Integer pageSize) {
String continuationObject, Integer pageSize, String filePrefix) {
// TODO 优化成查一次数据库
return Mono.defer(() -> {
List<S3ListResult.ObjectVo> s3Objects = new ArrayList<>();
@@ -172,7 +176,8 @@ public class S3LinkServiceImpl implements S3LinkService {
return Flux.defer(() -> Flux.just(
new TokenState(null, currToken.get() == null ? "" : currToken.get())))
.flatMap(tokenState -> listObjects(policyName, tokenState.nextToken, pageSize))
.flatMap(tokenState -> listObjects(policyName, tokenState.nextToken,
pageSize, filePrefix))
.flatMap(s3ListResult -> {
var filteredObjects = s3ListResult.getObjects();
if (!continuationObjectMatched.get()) {
@@ -267,4 +272,18 @@ public class S3LinkServiceImpl implements S3LinkService {
.flatMap(func);
}
String buildPrefix(String finalLocation, String filePrefix) {
if (StringUtils.isBlank(finalLocation) && StringUtils.isBlank(filePrefix)) {
return null;
}
StringBuilder sb = new StringBuilder();
if (StringUtils.isNotBlank(finalLocation)) {
sb.append(finalLocation).append("/");
}
if (StringUtils.isNotBlank(filePrefix)) {
sb.append(filePrefix);
}
return sb.toString();
}
}

View File

@@ -1,8 +1,8 @@
package run.halo.s3os;
import org.pf4j.PluginWrapper;
import org.springframework.stereotype.Component;
import run.halo.app.plugin.BasePlugin;
import run.halo.app.plugin.PluginContext;
/**
* @author johnniang
@@ -11,8 +11,8 @@ import run.halo.app.plugin.BasePlugin;
@Component
public class S3OsPlugin extends BasePlugin {
public S3OsPlugin(PluginWrapper wrapper) {
super(wrapper);
public S3OsPlugin(PluginContext pluginContext) {
super(pluginContext);
}
@Override

View File

@@ -46,9 +46,10 @@ metadata:
name: role-template-s3os-policy-config-validation
labels:
halo.run/role-template: "true"
halo.run/hidden: "true"
rbac.authorization.halo.run/aggregate-to-role-template-manage-configmaps: "true"
rules:
- apiGroups: ["s3os.halo.run"]
resources: ["policies/validation"]
resourceNames: ["s3"]
verbs: [ "create" ]
verbs: [ "create" ]

View File

@@ -7,8 +7,5 @@ spec:
- group: basic
label: 使用提示
formSchema:
- $formkit: text
help: 请前往 “附件 - 存储策略” 添加策略
label: 此处不用设置,请前往 “附件 - 存储策略” 添加策略
name: text
placeholder: 此处不用设置,请前往 “附件 - 存储策略” 添加策略
- $el: p
children: 请前往 “附件 - 存储策略” 添加策略

View File

@@ -6,12 +6,14 @@ spec:
enabled: true
requires: ">=2.14.0"
author:
name: Halo OSS Team
name: Halo
website: https://github.com/halo-dev
logo: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAABUdJREFUeF7tmmtoXFUQx3+zMYGi/RJRULRFsL6oim2klVK0CrVqKxjoJ0Vt1bR7t6lURagiFlGpFPogydklVo1i/VB84QMfqFijpflQrI+IYj7FF+0HMbUEI82O3NNtzG6y956bvXd3IXvgksCd+c/M/8w598ycFWb5kFkePw0CGhkwyxloLIFZngCNTbCxBBpLoI4Y0A6aydNKC62kaAXGSDHCv4zQzIh0MRa3uzVdAppmMSmWoVyL0IZyWUiA3wL7gI/FcCQOMmpCQCHwDpSOCoIYQOmWLK9WgFHdr4Bu4wyOsR14uBKni3SFdxhnl+T4fCaYVcsAzbCEPNsRbpiJow46u8TwkINcCX9RNWYgr2nWIrwAzJ2BehSVAVpYJbv5y1Up8QwoBL/f1aGY5JaJ4aALVqIEqMc24EkXRxKQmSeGX8JwEyNAPR4AesMcSPD9ECdpk15GgmwkQoB2spxxPgWaEwzQBfptMdxRfQI8PgJWuniYuIyyQbLlMzH2DFCPDUAu8cDcDRwWQ1s58VgJ0E7OYZyvgAXu/lVBMiAL4iXA4z5gbxVCimpiQAxLp1OKl4AMfSj3RPWuKvJ5FkmOr0ttxUuAx9/AWVUJKKoR5QnJ8nRsBOh65tJiy9nFYJ+fwB586nUcEsN1FROgHncDG2EqWIWR/wpcUCFGsPooc6SPfyYLOS8BzbAI5RlgVQJObrXdH9iZAPb/kCkul25+jEyAejboDxJyLiMG42Orx2fAioTs+LC3iOHDSARohs0oexJxStgtPWw5jZ34IUpJS7b4kBa4BDTDTSifJBI8vC+G1ZOxdQtzGGMIOD8hm1vF2I7UxAgmwLNpn8SaHyTPjZLjWGmg6tl9YCIrYiVCuVOyvOZEQGG3fzlWB06BjZJipXTbI/OUoWmuQvgmAbs+5HIxfOlKwCFgSQKOrBNDXxCuerwH3Ba77ZPMl16GQwnQjSwkxXexO6A8JdnwDpF6tANvxG1fzNQu+LR7QEKtrFfEuNcJ6vEzcHGMJEzZdH3scgRkC6e9eOwr/Qi3iuGEK6B6PAo85yofKid40oMfV9EoR8CbENxKCjVYvNCWSg8DUXQKn8TRKDqBstOs/6AMiLelVaYUDQtO0zyPcH+YnMP7d8Vw+3Ry5TLgJeBeB2BXEX8mDwDHEUZQWzafEBNcPca2FwlrpMd+WaaM6QnI8CyKX6AkN/KsmHyfZ9voQjfKUbDPOMoCxF6TVzLKzn75JZBmOcIXlVgN0y39JGka/yOVDtOL/D5g9ssS4L9Qz5aNl0Y26KZwRAzXTBZVj8PAIjd1Z6ntYoIzuWwtoBkeK9T/ztacBUurwA1cQRODzvpugvvEcFeYaHkCHuFMRvGPwwvDQCK+P0qexZLjt9N6mmE9am+P4xnKn5LlbBew4GowzTqEF12AnGXytEuOtyaC30aKY7YRcr0zRrDg92K40hUrtCWmGfajrHUFDJHzf9tzc8na9zu1j8eCL/RKj72Zch6hBPhI6tEFbHJGnV5wSEzxjVGMrbZBlB2SJXL57kRAgQS/G7wDODcyEUKX9LC5ZOb9+wO/lV5J9+c4sIsmdkoX/v+RhzMBloRNXESeB8E+4UNtSbtHsvSXCqt/1oB2xNYc88PBiiT8uuIgTeyVLn6IqFskHomAiY0rwyUoa1BWI3YGzyu8+wPld1K8Tp5+yeL/ri90aCdXM26JuBCYN/GcOjYPIwzbv3CYJg5IF/4dQixjRgTEYrlOQBoE1MlE1MyNRgbUjPo6MdzIgDqZiJq50ciAmlFfJ4YbGVAnE1EzN2Z9BvwH8mZfUOuuxhIAAAAASUVORK5CYII=
settingName: s3os-settings
configMapName: s3os-configMap
homepage: https://github.com/halo-dev/plugin-s3
homepage: https://www.halo.run/store/apps/app-Qxhpp
repo: https://github.com/halo-dev/plugin-s3
issues: https://github.com/halo-dev/plugin-s3/issues
displayName: "对象存储Amazon S3 协议)"
description: "提供兼容 Amazon S3 协议的对象存储策略,兼容阿里云、腾讯云、七牛云等"
license: