mirror of https://github.com/halo-dev/halo
feat: add the ability to install plugins remotely via URI (#3963)
#### What type of PR is this? /kind feature /area core /area console /milestone 2.6.x /kind api-change #### What this PR does / why we need it: 支持通过 URI 远程安装和升级插件 how to test it? 1. 测试插件安装 ```shell curl -u admin:admin -X POST http://localhost:8090/apis/api.console.halo.run/v1alpha1/plugins/-/install-from-uri --data '{ "uri": "https://halo.run/apis/api.store.halo.run/v1alpha1/applications/app-KhIVw/releases/app-release-canxF/download/app-release-canxF-znFre" }' ``` 2. 测试插件升级 ```shell curl -u admin:admin -X POST http://localhost:8090/apis/api.console.halo.run/v1alpha1/plugins/PluginFeed/upgrade-from-uri --data '{ "uri": "https://halo.run/apis/api.store.halo.run/v1alpha1/applications/app-KhIVw/releases/app-release-canxF/download/app-release-canxF-znFre" }' ``` #### Which issue(s) this PR fixes: Fixes #2292 #### Does this PR introduce a user-facing change? ```release-note 支持通过 URI 远程安装和升级插件 ```pull/4005/head
parent
f5493a6d86
commit
710261b035
|
@ -1,6 +1,7 @@
|
||||||
package run.halo.app.core.extension.endpoint;
|
package run.halo.app.core.extension.endpoint;
|
||||||
|
|
||||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
|
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
|
||||||
|
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
||||||
|
@ -17,6 +18,8 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
@ -38,6 +41,7 @@ import org.springframework.http.codec.multipart.FilePart;
|
||||||
import org.springframework.http.codec.multipart.FormFieldPart;
|
import org.springframework.http.codec.multipart.FormFieldPart;
|
||||||
import org.springframework.http.codec.multipart.Part;
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
|
@ -56,6 +60,10 @@ import run.halo.app.extension.Comparators;
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
||||||
|
import run.halo.app.infra.ReactiveUrlDataBufferFetcher;
|
||||||
|
import run.halo.app.infra.exception.ThemeInstallationException;
|
||||||
|
import run.halo.app.infra.exception.ThemeUpgradeException;
|
||||||
|
import run.halo.app.infra.utils.DataBufferUtils;
|
||||||
import run.halo.app.plugin.PluginNotFoundException;
|
import run.halo.app.plugin.PluginNotFoundException;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -67,6 +75,8 @@ public class PluginEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
private final PluginService pluginService;
|
private final PluginService pluginService;
|
||||||
|
|
||||||
|
private final ReactiveUrlDataBufferFetcher reactiveUrlDataBufferFetcher;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> endpoint() {
|
public RouterFunction<ServerResponse> endpoint() {
|
||||||
final var tag = "api.console.halo.run/v1alpha1/Plugin";
|
final var tag = "api.console.halo.run/v1alpha1/Plugin";
|
||||||
|
@ -83,6 +93,39 @@ public class PluginEndpoint implements CustomEndpoint {
|
||||||
))
|
))
|
||||||
.response(responseBuilder().implementation(Plugin.class))
|
.response(responseBuilder().implementation(Plugin.class))
|
||||||
)
|
)
|
||||||
|
.POST("plugins/-/install-from-uri", this::installFromUri,
|
||||||
|
builder -> builder.operationId("InstallPluginFromUri")
|
||||||
|
.description("Install a plugin from uri.")
|
||||||
|
.tag(tag)
|
||||||
|
.requestBody(requestBodyBuilder()
|
||||||
|
.required(true)
|
||||||
|
.content(contentBuilder()
|
||||||
|
.mediaType(MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
.schema(schemaBuilder()
|
||||||
|
.implementation(InstallFromUriRequest.class))
|
||||||
|
))
|
||||||
|
.response(responseBuilder()
|
||||||
|
.implementation(Plugin.class))
|
||||||
|
)
|
||||||
|
.POST("plugins/{name}/upgrade-from-uri", this::upgradeFromUri,
|
||||||
|
builder -> builder.operationId("UpgradePluginFromUri")
|
||||||
|
.description("Upgrade a plugin from uri.")
|
||||||
|
.tag(tag)
|
||||||
|
.parameter(parameterBuilder()
|
||||||
|
.in(ParameterIn.PATH)
|
||||||
|
.name("name")
|
||||||
|
.required(true)
|
||||||
|
)
|
||||||
|
.requestBody(requestBodyBuilder()
|
||||||
|
.required(true)
|
||||||
|
.content(contentBuilder()
|
||||||
|
.mediaType(MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
.schema(schemaBuilder()
|
||||||
|
.implementation(UpgradeFromUriRequest.class))
|
||||||
|
))
|
||||||
|
.response(responseBuilder()
|
||||||
|
.implementation(Plugin.class))
|
||||||
|
)
|
||||||
.POST("plugins/{name}/upgrade", contentType(MediaType.MULTIPART_FORM_DATA),
|
.POST("plugins/{name}/upgrade", contentType(MediaType.MULTIPART_FORM_DATA),
|
||||||
this::upgrade, builder -> builder.operationId("UpgradePlugin")
|
this::upgrade, builder -> builder.operationId("UpgradePlugin")
|
||||||
.description("Upgrade a plugin by uploading a Jar file")
|
.description("Upgrade a plugin by uploading a Jar file")
|
||||||
|
@ -177,6 +220,57 @@ public class PluginEndpoint implements CustomEndpoint {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> upgradeFromUri(ServerRequest request) {
|
||||||
|
final var name = request.pathVariable("name");
|
||||||
|
return request.bodyToMono(UpgradeFromUriRequest.class)
|
||||||
|
.flatMap(upgradeRequest -> Mono.fromCallable(() -> DataBufferUtils.toInputStream(
|
||||||
|
reactiveUrlDataBufferFetcher.fetch(upgradeRequest.uri())))
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
|
.onErrorMap(throwable -> {
|
||||||
|
log.error("Failed to fetch plugin file from uri.", throwable);
|
||||||
|
return new ThemeUpgradeException("Failed to fetch plugin file from uri.", null,
|
||||||
|
null);
|
||||||
|
})
|
||||||
|
.flatMap(inputStream -> Mono.usingWhen(
|
||||||
|
transferToTemp(inputStream),
|
||||||
|
(path) -> pluginService.upgrade(name, path),
|
||||||
|
this::deleteFileIfExists)
|
||||||
|
)
|
||||||
|
.flatMap(theme -> ServerResponse.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.bodyValue(theme)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> installFromUri(ServerRequest request) {
|
||||||
|
return request.bodyToMono(InstallFromUriRequest.class)
|
||||||
|
.flatMap(installRequest -> Mono.fromCallable(() -> DataBufferUtils.toInputStream(
|
||||||
|
reactiveUrlDataBufferFetcher.fetch(installRequest.uri())))
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
|
.doOnError(throwable -> {
|
||||||
|
log.error("Failed to fetch plugin file from uri.", throwable);
|
||||||
|
throw new ThemeInstallationException("Failed to fetch plugin file from uri.", null,
|
||||||
|
null);
|
||||||
|
})
|
||||||
|
.flatMap(inputStream -> Mono.usingWhen(
|
||||||
|
transferToTemp(inputStream),
|
||||||
|
pluginService::install,
|
||||||
|
this::deleteFileIfExists)
|
||||||
|
)
|
||||||
|
.flatMap(theme -> ServerResponse.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.bodyValue(theme)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record InstallFromUriRequest(@Schema(requiredMode = REQUIRED) URI uri) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UpgradeFromUriRequest(@Schema(requiredMode = REQUIRED) URI uri) {
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<ServerResponse> reload(ServerRequest serverRequest) {
|
private Mono<ServerResponse> reload(ServerRequest serverRequest) {
|
||||||
var name = serverRequest.pathVariable("name");
|
var name = serverRequest.pathVariable("name");
|
||||||
return ServerResponse.ok().body(pluginService.reload(name), Plugin.class);
|
return ServerResponse.ok().body(pluginService.reload(name), Plugin.class);
|
||||||
|
@ -508,4 +602,11 @@ public class PluginEndpoint implements CustomEndpoint {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<Path> transferToTemp(InputStream inputStream) {
|
||||||
|
return Mono.fromCallable(() -> {
|
||||||
|
Path tempFile = Files.createTempFile("halo-plugins", ".jar");
|
||||||
|
FileCopyUtils.copy(inputStream, Files.newOutputStream(tempFile));
|
||||||
|
return tempFile;
|
||||||
|
}).subscribeOn(Schedulers.boundedElastic());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.ServerWebInputException;
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
import reactor.core.Exceptions;
|
import reactor.core.Exceptions;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
import reactor.util.retry.Retry;
|
import reactor.util.retry.Retry;
|
||||||
import run.halo.app.core.extension.Setting;
|
import run.halo.app.core.extension.Setting;
|
||||||
import run.halo.app.core.extension.Theme;
|
import run.halo.app.core.extension.Theme;
|
||||||
|
@ -247,6 +248,7 @@ public class ThemeEndpoint implements CustomEndpoint {
|
||||||
.flatMap(upgradeRequest -> Mono.fromCallable(() -> DataBufferUtils.toInputStream(
|
.flatMap(upgradeRequest -> Mono.fromCallable(() -> DataBufferUtils.toInputStream(
|
||||||
reactiveUrlDataBufferFetcher.fetch(upgradeRequest.uri())))
|
reactiveUrlDataBufferFetcher.fetch(upgradeRequest.uri())))
|
||||||
)
|
)
|
||||||
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
.doOnError(throwable -> {
|
.doOnError(throwable -> {
|
||||||
log.error("Failed to fetch zip file from uri.", throwable);
|
log.error("Failed to fetch zip file from uri.", throwable);
|
||||||
throw new ThemeUpgradeException("Failed to fetch zip file from uri.", null,
|
throw new ThemeUpgradeException("Failed to fetch zip file from uri.", null,
|
||||||
|
@ -268,6 +270,7 @@ public class ThemeEndpoint implements CustomEndpoint {
|
||||||
.flatMap(installRequest -> Mono.fromCallable(() -> DataBufferUtils.toInputStream(
|
.flatMap(installRequest -> Mono.fromCallable(() -> DataBufferUtils.toInputStream(
|
||||||
reactiveUrlDataBufferFetcher.fetch(installRequest.uri())))
|
reactiveUrlDataBufferFetcher.fetch(installRequest.uri())))
|
||||||
)
|
)
|
||||||
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
.doOnError(throwable -> {
|
.doOnError(throwable -> {
|
||||||
log.error("Failed to fetch zip file from uri.", throwable);
|
log.error("Failed to fetch zip file from uri.", throwable);
|
||||||
throw new ThemeInstallationException("Failed to fetch zip file from uri.", null,
|
throw new ThemeInstallationException("Failed to fetch zip file from uri.", null,
|
||||||
|
|
|
@ -16,7 +16,8 @@ rules:
|
||||||
resources: [ "plugins" ]
|
resources: [ "plugins" ]
|
||||||
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
|
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
|
||||||
- apiGroups: [ "api.console.halo.run" ]
|
- apiGroups: [ "api.console.halo.run" ]
|
||||||
resources: [ "plugins/upgrade", "plugins/resetconfig", "plugins/config", "plugins/reload" ]
|
resources: [ "plugins/upgrade", "plugins/resetconfig", "plugins/config", "plugins/reload",
|
||||||
|
"plugins/install-from-uri", "plugins/upgrade-from-uri" ]
|
||||||
verbs: [ "*" ]
|
verbs: [ "*" ]
|
||||||
- apiGroups: [ "api.console.halo.run" ]
|
- apiGroups: [ "api.console.halo.run" ]
|
||||||
resources: [ "plugin-presets" ]
|
resources: [ "plugin-presets" ]
|
||||||
|
|
|
@ -40,11 +40,15 @@ import {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { ConfigMap } from "../models";
|
import { ConfigMap } from "../models";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
import { InstallFromUriRequest } from "../models";
|
||||||
|
// @ts-ignore
|
||||||
import { Plugin } from "../models";
|
import { Plugin } from "../models";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { PluginList } from "../models";
|
import { PluginList } from "../models";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Setting } from "../models";
|
import { Setting } from "../models";
|
||||||
|
// @ts-ignore
|
||||||
|
import { UpgradeFromUriRequest } from "../models";
|
||||||
/**
|
/**
|
||||||
* ApiConsoleHaloRunV1alpha1PluginApi - axios parameter creator
|
* ApiConsoleHaloRunV1alpha1PluginApi - axios parameter creator
|
||||||
* @export
|
* @export
|
||||||
|
@ -231,6 +235,67 @@ export const ApiConsoleHaloRunV1alpha1PluginApiAxiosParamCreator = function (
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Install a plugin from uri.
|
||||||
|
* @param {InstallFromUriRequest} installFromUriRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
installPluginFromUri: async (
|
||||||
|
installFromUriRequest: InstallFromUriRequest,
|
||||||
|
options: AxiosRequestConfig = {}
|
||||||
|
): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'installFromUriRequest' is not null or undefined
|
||||||
|
assertParamExists(
|
||||||
|
"installPluginFromUri",
|
||||||
|
"installFromUriRequest",
|
||||||
|
installFromUriRequest
|
||||||
|
);
|
||||||
|
const localVarPath = `/apis/api.console.halo.run/v1alpha1/plugins/-/install-from-uri`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = {
|
||||||
|
method: "POST",
|
||||||
|
...baseOptions,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication BasicAuth required
|
||||||
|
// http basic authentication required
|
||||||
|
setBasicAuthToObject(localVarRequestOptions, configuration);
|
||||||
|
|
||||||
|
// authentication BearerAuth required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration);
|
||||||
|
|
||||||
|
localVarHeaderParameter["Content-Type"] = "application/json";
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions =
|
||||||
|
baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {
|
||||||
|
...localVarHeaderParameter,
|
||||||
|
...headersFromBaseOptions,
|
||||||
|
...options.headers,
|
||||||
|
};
|
||||||
|
localVarRequestOptions.data = serializeDataIfNeeded(
|
||||||
|
installFromUriRequest,
|
||||||
|
localVarRequestOptions,
|
||||||
|
configuration
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* List all plugin presets in the system.
|
* List all plugin presets in the system.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -611,6 +676,75 @@ export const ApiConsoleHaloRunV1alpha1PluginApiAxiosParamCreator = function (
|
||||||
};
|
};
|
||||||
localVarRequestOptions.data = localVarFormParams;
|
localVarRequestOptions.data = localVarFormParams;
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Upgrade a plugin from uri.
|
||||||
|
* @param {string} name
|
||||||
|
* @param {UpgradeFromUriRequest} upgradeFromUriRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
upgradePluginFromUri: async (
|
||||||
|
name: string,
|
||||||
|
upgradeFromUriRequest: UpgradeFromUriRequest,
|
||||||
|
options: AxiosRequestConfig = {}
|
||||||
|
): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'name' is not null or undefined
|
||||||
|
assertParamExists("upgradePluginFromUri", "name", name);
|
||||||
|
// verify required parameter 'upgradeFromUriRequest' is not null or undefined
|
||||||
|
assertParamExists(
|
||||||
|
"upgradePluginFromUri",
|
||||||
|
"upgradeFromUriRequest",
|
||||||
|
upgradeFromUriRequest
|
||||||
|
);
|
||||||
|
const localVarPath =
|
||||||
|
`/apis/api.console.halo.run/v1alpha1/plugins/{name}/upgrade-from-uri`.replace(
|
||||||
|
`{${"name"}}`,
|
||||||
|
encodeURIComponent(String(name))
|
||||||
|
);
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = {
|
||||||
|
method: "POST",
|
||||||
|
...baseOptions,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication BasicAuth required
|
||||||
|
// http basic authentication required
|
||||||
|
setBasicAuthToObject(localVarRequestOptions, configuration);
|
||||||
|
|
||||||
|
// authentication BearerAuth required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration);
|
||||||
|
|
||||||
|
localVarHeaderParameter["Content-Type"] = "application/json";
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions =
|
||||||
|
baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {
|
||||||
|
...localVarHeaderParameter,
|
||||||
|
...headersFromBaseOptions,
|
||||||
|
...options.headers,
|
||||||
|
};
|
||||||
|
localVarRequestOptions.data = serializeDataIfNeeded(
|
||||||
|
upgradeFromUriRequest,
|
||||||
|
localVarRequestOptions,
|
||||||
|
configuration
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: toPathString(localVarUrlObj),
|
url: toPathString(localVarUrlObj),
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
|
@ -700,6 +834,30 @@ export const ApiConsoleHaloRunV1alpha1PluginApiFp = function (
|
||||||
configuration
|
configuration
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Install a plugin from uri.
|
||||||
|
* @param {InstallFromUriRequest} installFromUriRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async installPluginFromUri(
|
||||||
|
installFromUriRequest: InstallFromUriRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Plugin>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs =
|
||||||
|
await localVarAxiosParamCreator.installPluginFromUri(
|
||||||
|
installFromUriRequest,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
return createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* List all plugin presets in the system.
|
* List all plugin presets in the system.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -863,6 +1021,33 @@ export const ApiConsoleHaloRunV1alpha1PluginApiFp = function (
|
||||||
configuration
|
configuration
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Upgrade a plugin from uri.
|
||||||
|
* @param {string} name
|
||||||
|
* @param {UpgradeFromUriRequest} upgradeFromUriRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async upgradePluginFromUri(
|
||||||
|
name: string,
|
||||||
|
upgradeFromUriRequest: UpgradeFromUriRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Plugin>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs =
|
||||||
|
await localVarAxiosParamCreator.upgradePluginFromUri(
|
||||||
|
name,
|
||||||
|
upgradeFromUriRequest,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
return createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -924,6 +1109,20 @@ export const ApiConsoleHaloRunV1alpha1PluginApiFactory = function (
|
||||||
)
|
)
|
||||||
.then((request) => request(axios, basePath));
|
.then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Install a plugin from uri.
|
||||||
|
* @param {ApiConsoleHaloRunV1alpha1PluginApiInstallPluginFromUriRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
installPluginFromUri(
|
||||||
|
requestParameters: ApiConsoleHaloRunV1alpha1PluginApiInstallPluginFromUriRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): AxiosPromise<Plugin> {
|
||||||
|
return localVarFp
|
||||||
|
.installPluginFromUri(requestParameters.installFromUriRequest, options)
|
||||||
|
.then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* List all plugin presets in the system.
|
* List all plugin presets in the system.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -1025,6 +1224,24 @@ export const ApiConsoleHaloRunV1alpha1PluginApiFactory = function (
|
||||||
)
|
)
|
||||||
.then((request) => request(axios, basePath));
|
.then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Upgrade a plugin from uri.
|
||||||
|
* @param {ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUriRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
upgradePluginFromUri(
|
||||||
|
requestParameters: ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUriRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): AxiosPromise<Plugin> {
|
||||||
|
return localVarFp
|
||||||
|
.upgradePluginFromUri(
|
||||||
|
requestParameters.name,
|
||||||
|
requestParameters.upgradeFromUriRequest,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
.then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1084,6 +1301,20 @@ export interface ApiConsoleHaloRunV1alpha1PluginApiInstallPluginRequest {
|
||||||
readonly source?: string;
|
readonly source?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request parameters for installPluginFromUri operation in ApiConsoleHaloRunV1alpha1PluginApi.
|
||||||
|
* @export
|
||||||
|
* @interface ApiConsoleHaloRunV1alpha1PluginApiInstallPluginFromUriRequest
|
||||||
|
*/
|
||||||
|
export interface ApiConsoleHaloRunV1alpha1PluginApiInstallPluginFromUriRequest {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {InstallFromUriRequest}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1PluginApiInstallPluginFromUri
|
||||||
|
*/
|
||||||
|
readonly installFromUriRequest: InstallFromUriRequest;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request parameters for listPlugins operation in ApiConsoleHaloRunV1alpha1PluginApi.
|
* Request parameters for listPlugins operation in ApiConsoleHaloRunV1alpha1PluginApi.
|
||||||
* @export
|
* @export
|
||||||
|
@ -1224,6 +1455,27 @@ export interface ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginRequest {
|
||||||
readonly source?: string;
|
readonly source?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request parameters for upgradePluginFromUri operation in ApiConsoleHaloRunV1alpha1PluginApi.
|
||||||
|
* @export
|
||||||
|
* @interface ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUriRequest
|
||||||
|
*/
|
||||||
|
export interface ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUriRequest {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUri
|
||||||
|
*/
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {UpgradeFromUriRequest}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUri
|
||||||
|
*/
|
||||||
|
readonly upgradeFromUriRequest: UpgradeFromUriRequest;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ApiConsoleHaloRunV1alpha1PluginApi - object-oriented interface
|
* ApiConsoleHaloRunV1alpha1PluginApi - object-oriented interface
|
||||||
* @export
|
* @export
|
||||||
|
@ -1284,6 +1536,22 @@ export class ApiConsoleHaloRunV1alpha1PluginApi extends BaseAPI {
|
||||||
.then((request) => request(this.axios, this.basePath));
|
.then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a plugin from uri.
|
||||||
|
* @param {ApiConsoleHaloRunV1alpha1PluginApiInstallPluginFromUriRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1PluginApi
|
||||||
|
*/
|
||||||
|
public installPluginFromUri(
|
||||||
|
requestParameters: ApiConsoleHaloRunV1alpha1PluginApiInstallPluginFromUriRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
) {
|
||||||
|
return ApiConsoleHaloRunV1alpha1PluginApiFp(this.configuration)
|
||||||
|
.installPluginFromUri(requestParameters.installFromUriRequest, options)
|
||||||
|
.then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all plugin presets in the system.
|
* List all plugin presets in the system.
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -1394,4 +1662,24 @@ export class ApiConsoleHaloRunV1alpha1PluginApi extends BaseAPI {
|
||||||
)
|
)
|
||||||
.then((request) => request(this.axios, this.basePath));
|
.then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade a plugin from uri.
|
||||||
|
* @param {ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUriRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof ApiConsoleHaloRunV1alpha1PluginApi
|
||||||
|
*/
|
||||||
|
public upgradePluginFromUri(
|
||||||
|
requestParameters: ApiConsoleHaloRunV1alpha1PluginApiUpgradePluginFromUriRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
) {
|
||||||
|
return ApiConsoleHaloRunV1alpha1PluginApiFp(this.configuration)
|
||||||
|
.upgradePluginFromUri(
|
||||||
|
requestParameters.name,
|
||||||
|
requestParameters.upgradeFromUriRequest,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
.then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -733,6 +733,9 @@ core:
|
||||||
change_status:
|
change_status:
|
||||||
active_title: Are you sure you want to active this plugin?
|
active_title: Are you sure you want to active this plugin?
|
||||||
inactive_title: Are you sure you want to inactive this plugin?
|
inactive_title: Are you sure you want to inactive this plugin?
|
||||||
|
remote_download:
|
||||||
|
title: Remote download address detected, do you want to download?
|
||||||
|
description: "Please carefully verify whether this address can be trusted: {url}"
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
|
@ -750,6 +753,12 @@ core:
|
||||||
titles:
|
titles:
|
||||||
install: Install plugin
|
install: Install plugin
|
||||||
upgrade: Upgrade plugin ({display_name})
|
upgrade: Upgrade plugin ({display_name})
|
||||||
|
tabs:
|
||||||
|
local: Local
|
||||||
|
remote:
|
||||||
|
title: Remote
|
||||||
|
fields:
|
||||||
|
url: Remote URL
|
||||||
operations:
|
operations:
|
||||||
active_after_install:
|
active_after_install:
|
||||||
title: Install successful
|
title: Install successful
|
||||||
|
|
|
@ -733,6 +733,9 @@ core:
|
||||||
change_status:
|
change_status:
|
||||||
active_title: 确定要启用该插件吗?
|
active_title: 确定要启用该插件吗?
|
||||||
inactive_title: 确定要停用该插件吗?
|
inactive_title: 确定要停用该插件吗?
|
||||||
|
remote_download:
|
||||||
|
title: 检测到了远程下载地址,是否需要下载?
|
||||||
|
description: 请仔细鉴别此地址是否可信:{url}
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
|
@ -750,6 +753,12 @@ core:
|
||||||
titles:
|
titles:
|
||||||
install: 安装插件
|
install: 安装插件
|
||||||
upgrade: 升级插件({display_name})
|
upgrade: 升级插件({display_name})
|
||||||
|
tabs:
|
||||||
|
local: 本地上传
|
||||||
|
remote:
|
||||||
|
title: 远程下载
|
||||||
|
fields:
|
||||||
|
url: 下载地址
|
||||||
operations:
|
operations:
|
||||||
active_after_install:
|
active_after_install:
|
||||||
title: 安装成功
|
title: 安装成功
|
||||||
|
|
|
@ -733,6 +733,9 @@ core:
|
||||||
change_status:
|
change_status:
|
||||||
active_title: 確定要啟用該插件嗎?
|
active_title: 確定要啟用該插件嗎?
|
||||||
inactive_title: 確定要停用該插件嗎?
|
inactive_title: 確定要停用該插件嗎?
|
||||||
|
remote_download:
|
||||||
|
title: 偵測到遠端下載地址,是否需要下載?
|
||||||
|
description: 請仔細鑑別此地址是否可信:{url}
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
|
@ -750,6 +753,12 @@ core:
|
||||||
titles:
|
titles:
|
||||||
install: 安裝插件
|
install: 安裝插件
|
||||||
upgrade: 升級插件({display_name})
|
upgrade: 升級插件({display_name})
|
||||||
|
tabs:
|
||||||
|
local: 本地上傳
|
||||||
|
remote:
|
||||||
|
title: 遠端下載
|
||||||
|
fields:
|
||||||
|
url: 下載地址
|
||||||
operations:
|
operations:
|
||||||
active_after_install:
|
active_after_install:
|
||||||
title: 安裝成功
|
title: 安裝成功
|
||||||
|
|
|
@ -13,10 +13,11 @@ import {
|
||||||
VLoading,
|
VLoading,
|
||||||
VDropdown,
|
VDropdown,
|
||||||
VDropdownItem,
|
VDropdownItem,
|
||||||
|
Dialog,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import PluginListItem from "./components/PluginListItem.vue";
|
import PluginListItem from "./components/PluginListItem.vue";
|
||||||
import PluginUploadModal from "./components/PluginUploadModal.vue";
|
import PluginUploadModal from "./components/PluginUploadModal.vue";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref, onMounted } from "vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||||
|
@ -25,6 +26,7 @@ import { getNode } from "@formkit/core";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import type { Plugin } from "@halo-dev/api-client";
|
import type { Plugin } from "@halo-dev/api-client";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -146,12 +148,34 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
|
||||||
return deletingPlugins?.length ? 3000 : false;
|
return deletingPlugins?.length ? 3000 : false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// handle remote download url from route
|
||||||
|
const routeRemoteDownloadUrl = useRouteQuery<string | null>(
|
||||||
|
"remote-download-url"
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
if (routeRemoteDownloadUrl.value) {
|
||||||
|
Dialog.warning({
|
||||||
|
title: t("core.plugin.operations.remote_download.title"),
|
||||||
|
description: t("core.plugin.operations.remote_download.description", {
|
||||||
|
url: routeRemoteDownloadUrl.value,
|
||||||
|
}),
|
||||||
|
confirmText: t("core.common.buttons.download"),
|
||||||
|
cancelText: t("core.common.buttons.cancel"),
|
||||||
|
onConfirm() {
|
||||||
|
pluginInstall.value = true;
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
routeRemoteDownloadUrl.value = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<PluginUploadModal
|
<PluginUploadModal
|
||||||
v-if="currentUserHasPermission(['system:plugins:manage'])"
|
v-if="currentUserHasPermission(['system:plugins:manage'])"
|
||||||
v-model:visible="pluginInstall"
|
v-model:visible="pluginInstall"
|
||||||
@close="refetch()"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VPageHeader :title="$t('core.plugin.title')">
|
<VPageHeader :title="$t('core.plugin.title')">
|
||||||
|
@ -324,7 +348,7 @@ const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
|
||||||
role="list"
|
role="list"
|
||||||
>
|
>
|
||||||
<li v-for="plugin in data" :key="plugin.metadata.name">
|
<li v-for="plugin in data" :key="plugin.metadata.name">
|
||||||
<PluginListItem :plugin="plugin" @reload="refetch()" />
|
<PluginListItem :plugin="plugin" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
|
@ -32,10 +32,6 @@ const props = withDefaults(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(event: "reload"): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { plugin } = toRefs(props);
|
const { plugin } = toRefs(props);
|
||||||
|
|
||||||
const upgradeModal = ref(false);
|
const upgradeModal = ref(false);
|
||||||
|
@ -43,10 +39,6 @@ const upgradeModal = ref(false);
|
||||||
const { getFailedMessage, changeStatus, uninstall } =
|
const { getFailedMessage, changeStatus, uninstall } =
|
||||||
usePluginLifeCycle(plugin);
|
usePluginLifeCycle(plugin);
|
||||||
|
|
||||||
const onUpgradeModalClose = () => {
|
|
||||||
emit("reload");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResetSettingConfig = async () => {
|
const handleResetSettingConfig = async () => {
|
||||||
Dialog.warning({
|
Dialog.warning({
|
||||||
title: t("core.plugin.operations.reset.title"),
|
title: t("core.plugin.operations.reset.title"),
|
||||||
|
@ -73,11 +65,7 @@ const handleResetSettingConfig = async () => {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<PluginUploadModal
|
<PluginUploadModal v-model:visible="upgradeModal" :upgrade-plugin="plugin" />
|
||||||
v-model:visible="upgradeModal"
|
|
||||||
:upgrade-plugin="plugin"
|
|
||||||
@close="onUpgradeModalClose"
|
|
||||||
/>
|
|
||||||
<VEntity>
|
<VEntity>
|
||||||
<template #start>
|
<template #start>
|
||||||
<VEntityField>
|
<VEntityField>
|
||||||
|
|
|
@ -1,14 +1,25 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VModal, Dialog, Toast } from "@halo-dev/components";
|
import {
|
||||||
|
VModal,
|
||||||
|
Dialog,
|
||||||
|
Toast,
|
||||||
|
VTabs,
|
||||||
|
VTabItem,
|
||||||
|
VButton,
|
||||||
|
} from "@halo-dev/components";
|
||||||
import UppyUpload from "@/components/upload/UppyUpload.vue";
|
import UppyUpload from "@/components/upload/UppyUpload.vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { Plugin } from "@halo-dev/api-client";
|
import type { Plugin } from "@halo-dev/api-client";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch, nextTick } from "vue";
|
||||||
import type { SuccessResponse, ErrorResponse } from "@uppy/core";
|
import type { SuccessResponse, ErrorResponse } from "@uppy/core";
|
||||||
import type { UppyFile } from "@uppy/utils";
|
import type { UppyFile } from "@uppy/utils";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useQueryClient } from "@tanstack/vue-query";
|
||||||
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
|
import { submitForm } from "@formkit/core";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -57,8 +68,13 @@ const onUploaded = async (response: SuccessResponse) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugin = response.body as Plugin;
|
|
||||||
handleVisibleChange(false);
|
handleVisibleChange(false);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["plugins"] });
|
||||||
|
|
||||||
|
handleShowActiveModalAfterInstall(response.body as Plugin);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShowActiveModalAfterInstall = (plugin: Plugin) => {
|
||||||
Dialog.success({
|
Dialog.success({
|
||||||
title: t("core.plugin.upload_modal.operations.active_after_install.title"),
|
title: t("core.plugin.upload_modal.operations.active_after_install.title"),
|
||||||
description: t(
|
description: t(
|
||||||
|
@ -103,7 +119,16 @@ const PLUGIN_ALREADY_EXISTS_TYPE =
|
||||||
|
|
||||||
const onError = (file: UppyFile<unknown>, response: ErrorResponse) => {
|
const onError = (file: UppyFile<unknown>, response: ErrorResponse) => {
|
||||||
const body = response.body as PluginInstallationErrorResponse;
|
const body = response.body as PluginInstallationErrorResponse;
|
||||||
|
|
||||||
if (body.type === PLUGIN_ALREADY_EXISTS_TYPE) {
|
if (body.type === PLUGIN_ALREADY_EXISTS_TYPE) {
|
||||||
|
handleCatchExistsException(body, file.data as File);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCatchExistsException = async (
|
||||||
|
error: PluginInstallationErrorResponse,
|
||||||
|
file?: File
|
||||||
|
) => {
|
||||||
Dialog.info({
|
Dialog.info({
|
||||||
title: t(
|
title: t(
|
||||||
"core.plugin.upload_modal.operations.existed_during_installation.title"
|
"core.plugin.upload_modal.operations.existed_during_installation.title"
|
||||||
|
@ -114,17 +139,27 @@ const onError = (file: UppyFile<unknown>, response: ErrorResponse) => {
|
||||||
confirmText: t("core.common.buttons.confirm"),
|
confirmText: t("core.common.buttons.confirm"),
|
||||||
cancelText: t("core.common.buttons.cancel"),
|
cancelText: t("core.common.buttons.cancel"),
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
|
if (activeTabId.value === "local") {
|
||||||
await apiClient.plugin.upgradePlugin({
|
await apiClient.plugin.upgradePlugin({
|
||||||
name: body.pluginName,
|
name: error.pluginName,
|
||||||
file: file.data as File,
|
file: file,
|
||||||
});
|
});
|
||||||
|
} else if (activeTabId.value === "remote") {
|
||||||
|
await apiClient.plugin.upgradePluginFromUri({
|
||||||
|
name: error.pluginName,
|
||||||
|
upgradeFromUriRequest: {
|
||||||
|
uri: remoteDownloadUrl.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error("Unknown tab id");
|
||||||
|
}
|
||||||
|
|
||||||
Toast.success(t("core.common.toast.upgrade_success"));
|
Toast.success(t("core.common.toast.upgrade_success"));
|
||||||
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -140,14 +175,80 @@ watch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// remote download
|
||||||
|
const activeTabId = ref("local");
|
||||||
|
const remoteDownloadUrl = ref("");
|
||||||
|
const downloading = ref(false);
|
||||||
|
|
||||||
|
const handleDownloadPlugin = async () => {
|
||||||
|
try {
|
||||||
|
downloading.value = true;
|
||||||
|
if (props.upgradePlugin) {
|
||||||
|
await apiClient.plugin.upgradePluginFromUri({
|
||||||
|
name: props.upgradePlugin.metadata.name,
|
||||||
|
upgradeFromUriRequest: {
|
||||||
|
uri: remoteDownloadUrl.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Toast.success(t("core.common.toast.upgrade_success"));
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: plugin } = await apiClient.plugin.installPluginFromUri({
|
||||||
|
installFromUriRequest: {
|
||||||
|
uri: remoteDownloadUrl.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
handleVisibleChange(false);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["plugins"] });
|
||||||
|
|
||||||
|
handleShowActiveModalAfterInstall(plugin);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
} catch (error: any) {
|
||||||
|
const data = error?.response.data as PluginInstallationErrorResponse;
|
||||||
|
if (data?.type === PLUGIN_ALREADY_EXISTS_TYPE) {
|
||||||
|
handleCatchExistsException(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("Failed to download plugin", error);
|
||||||
|
} finally {
|
||||||
|
routeRemoteDownloadUrl.value = null;
|
||||||
|
downloading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle remote download url from route
|
||||||
|
const routeRemoteDownloadUrl = useRouteQuery<string | null>(
|
||||||
|
"remote-download-url"
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(visible) => {
|
||||||
|
if (routeRemoteDownloadUrl.value && visible) {
|
||||||
|
activeTabId.value = "remote";
|
||||||
|
remoteDownloadUrl.value = routeRemoteDownloadUrl.value;
|
||||||
|
nextTick(() => {
|
||||||
|
submitForm("plugin-remote-download-form");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VModal
|
<VModal
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
:width="600"
|
:width="600"
|
||||||
:title="modalTitle"
|
:title="modalTitle"
|
||||||
|
:centered="false"
|
||||||
@update:visible="handleVisibleChange"
|
@update:visible="handleVisibleChange"
|
||||||
>
|
>
|
||||||
|
<VTabs v-model:active-id="activeTabId" type="outline" class="!rounded-none">
|
||||||
|
<VTabItem id="local" :label="$t('core.plugin.upload_modal.tabs.local')">
|
||||||
<UppyUpload
|
<UppyUpload
|
||||||
v-if="uploadVisible"
|
v-if="uploadVisible"
|
||||||
:restrictions="{
|
:restrictions="{
|
||||||
|
@ -159,5 +260,35 @@ watch(
|
||||||
@uploaded="onUploaded"
|
@uploaded="onUploaded"
|
||||||
@error="onError"
|
@error="onError"
|
||||||
/>
|
/>
|
||||||
|
</VTabItem>
|
||||||
|
<VTabItem
|
||||||
|
id="remote"
|
||||||
|
:label="$t('core.plugin.upload_modal.tabs.remote.title')"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
id="plugin-remote-download-form"
|
||||||
|
name="plugin-remote-download-form"
|
||||||
|
type="form"
|
||||||
|
:preserve="true"
|
||||||
|
@submit="handleDownloadPlugin"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
v-model="remoteDownloadUrl"
|
||||||
|
:label="$t('core.plugin.upload_modal.tabs.remote.fields.url')"
|
||||||
|
type="text"
|
||||||
|
></FormKit>
|
||||||
|
</FormKit>
|
||||||
|
|
||||||
|
<div class="pt-5">
|
||||||
|
<VButton
|
||||||
|
:loading="downloading"
|
||||||
|
type="secondary"
|
||||||
|
@click="$formkit.submit('plugin-remote-download-form')"
|
||||||
|
>
|
||||||
|
{{ $t("core.common.buttons.download") }}
|
||||||
|
</VButton>
|
||||||
|
</div>
|
||||||
|
</VTabItem>
|
||||||
|
</VTabs>
|
||||||
</VModal>
|
</VModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue