Spark Workbench Dashboard Gadget 플러그인 개발 가이드
이 문서는 Spark Workbench Dashboard Gadget을 확장하려는 개발자를 위한 가이드입니다. Gadget 플러그인은 자체 포함 구조가 되어야 합니다. 백엔드 확장점, 프론트엔드 Vue 컴포넌트, 생성된 프론트엔드 산출물을 모두 같은 플러그인 모듈에 두고, 플랫폼 호스트는 로딩과 실행만 담당합니다.
1. Gadget 플러그인 모델
Spark Workbench Gadget은 두 부분으로 구성됩니다.
- 백엔드 확장점: Lattice Ability를 통해 Gadget 설명 정보를 등록하고 런타임 데이터를 렌더링합니다.
- 프론트엔드 컴포넌트: Vue 컴포넌트로 데이터를 표시하며, UMD 리소스로 패키징되어 Workbench가 동적으로 로드합니다.
기본 플러그인은 다음 위치에 있습니다.
spark-plugins/spark-workbench-default-plugin/
이 모듈은 Java와 프론트엔드 코드를 함께 포함합니다. 타사 플러그인도 같은 구조를 따르는 것이 좋습니다.
2. 권장 구조
my-workbench-gadget-plugin/
pom.xml
src/main/java/
.../MyGadgetRegistryExt.java
src/main/frontend/
package.json
vite.config.js
src/
index.js
GadgetMyChart.vue
GadgetMyList.vue
MyGadgetRegistryExt.java는 Gadget 타입, 제목, 카테고리, 아이콘, 프론트엔드 리소스 URL, 런타임 데이터를 정의합니다.src/main/frontend/src/index.js는 Vue 컴포넌트를 Workbench 호스트에 등록합니다.vite.config.js는 library 모드로 UMD 번들을 만들고 플러그인 jar의 정적 리소스 디렉터리로 출력합니다.
3. 백엔드 확장점
SparkEraTaskGadgetRegistryExt를 구현합니다. 일반적으로 빈 구현을 상속합니다.
@Realization(codes = "my.workbench.gadget")
public class MyGadgetRegistryExt extends BlankSparkEraTaskGadgetRegistryExt {
private static final String FRONTEND_RESOURCE =
"/spark-gadgets/my-workbench-gadget/index.umd.js";
@Override
public List<SparkEraTaskGadgetDescriptor> listGadgetDescriptors(SparkEraTaskGadgetCatalogContext context) {
SparkEraTaskGadgetDescriptor descriptor = new SparkEraTaskGadgetDescriptor();
descriptor.setCode("myChart");
descriptor.setTitle("My Chart");
descriptor.setCategory("charts");
descriptor.setIcon("bar");
descriptor.setComponent("spark$gadget-my-chart");
descriptor.setFrontendResource(FRONTEND_RESOURCE);
descriptor.setConfigurable(true);
return List.of(descriptor);
}
@Override
public SparkEraTaskGadgetRenderResult renderGadget(SparkEraTaskGadgetRenderContext context) {
if (!StringUtils.equals(context.getGadgetType(), "myChart")) {
return null;
}
SparkEraTaskGadgetRenderResult result =
SparkEraTaskGadgetRenderResult.of("myChart", "spark$gadget-my-chart");
result.getData().put("bars", List.of(Map.of("label", "Open", "value", 12)));
return result;
}
}
핵심 필드:
code: 전역적으로 유일한 Gadget 타입 코드입니다.component: 프론트엔드 컴포넌트 등록 key입니다.spark$gadget-xxx형식을 권장합니다.frontendResource: 플러그인 프론트엔드 진입 JS의 런타임 URL입니다.configurable: Dashboard에서 설정 패널을 열 수 있는지 여부입니다.
4. 프론트엔드 자체 등록
프론트엔드 진입 파일은 스크립트가 로드된 뒤 컴포넌트를 등록합니다.
import GadgetMyChart from "./GadgetMyChart.vue";
const COMPONENTS = {
"spark$gadget-my-chart": GadgetMyChart
};
const api = typeof window === "undefined" ? null : window.__SPARK_DASHBOARD__;
if (api && typeof api.registerGadgetComponent === "function") {
Object.entries(COMPONENTS).forEach(([key, component]) => {
api.registerGadgetComponent(key, component);
});
}
Workbench는 Dashboard catalog에서 frontendResource를 수집하고 중복 제거 후 <script>를 동적으로 삽입합니다. 스크립트 실행 후 컴포넌트는 호스트 레지스트리에 등록됩니다.
5. Vite 빌드 설정
플러그인 프론트엔드는 Vite library 모드를 사용하고 Vue를 external로 둡니다. 플러그인은 자체 Vue를 번들링하지 않고 호스트의 window.__SPARK_VUE__를 사용합니다.
import { resolve } from "node:path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
build: {
outDir: resolve(__dirname, "../../../target/classes/META-INF/resources/spark-gadgets/my-workbench-gadget"),
lib: {
entry: resolve(__dirname, "src/index.js"),
name: "MyWorkbenchGadget",
formats: ["umd"],
fileName: () => "index.umd.js",
cssFileName: "index"
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "__SPARK_VUE__"
}
}
}
}
});
빌드 산출물:
target/classes/META-INF/resources/spark-gadgets/my-workbench-gadget/index.umd.js
target/classes/META-INF/resources/spark-gadgets/my-workbench-gadget/index.css
Spring Boot는 META-INF/resources를 정적 리소스로 제공하므로 런타임 URL은 다음과 같습니다.
/spark-gadgets/my-workbench-gadget/index.umd.js
6. Maven 패키징
플러그인 pom.xml에서 frontend-maven-plugin을 사용하여 prepare-package 단계에 프론트엔드를 빌드합니다. 최종 plugin jar에는 Java 확장점과 프론트엔드 리소스가 함께 포함됩니다.
7. 컴포넌트 개발 규칙
- 컴포넌트는
data,t등 호스트가 전달한 props만 받으며 플랫폼 API를 직접 호출하지 않습니다. - 스타일은
scoped와 Spark 테마 변수(--text-main,--card-bg,--primary)를 사용합니다. - 플러그인 내부에서 별도 Vue app을 만들지 않습니다.
- 두 번째 Vue 런타임을 번들링하지 않습니다.
- 데이터는
renderGadget이 반환하고, 프론트엔드는 표시만 담당합니다. - 설정값은 Gadget instance의
config에 저장하고 백엔드 렌더링 시 읽습니다.
8. 검증 체크리스트
cd src/main/frontend
npm ci
npm run build
그 다음 플러그인 모듈에서 실행합니다.
mvn -DskipTests package
jar 안에 다음 파일이 있는지 확인합니다.
META-INF/resources/spark-gadgets/<plugin-code>/index.umd.js
META-INF/resources/spark-gadgets/<plugin-code>/index.css
Spark Console 시작 후 다음 URL을 엽니다.
/spark-gadgets/<plugin-code>/index.umd.js
JavaScript가 반환되면 플러그인 프론트엔드가 올바르게 제공된 것입니다.
9. 문제 해결
Gadget이 Unsupported로 표시됨
component key와 index.js에 등록한 key가 다르거나 frontendResource가 로드되지 않았을 수 있습니다.
스타일이 적용되지 않음
Vite가 index.css를 생성했는지, 진입 스크립트가 CSS를 삽입했는지 확인합니다.
Vue 런타임 오류
Vite에서 Vue가 external인지, output.globals.vue가 __SPARK_VUE__를 가리키는지 확인합니다. 플러그인은 자체 Vue를 포함하면 안 됩니다.
Catalog에 플러그인이 표시되지 않음
플러그인 jar가 Spark Console 의존성에 포함되어 있고 @Realization(codes = "...") business code가 Lattice에 의해 스캔되는지 확인합니다.
10. 제공 원칙
Dashboard Gadget 플러그인은 하나의 자체 포함 artifact여야 합니다.
- Java 확장점은 플러그인 모듈에 둡니다.
- Vue 컴포넌트도 플러그인 모듈에 둡니다.
- 프론트엔드 빌드 산출물은 플러그인 jar에 포함합니다.
- 플랫폼 호스트는 레지스트리, 동적 로더, 카드 셸, fallback 컴포넌트만 유지합니다.
이 모델을 사용하면 비즈니스 Gadget을 추가할 때 플랫폼 코어를 수정하지 않고 플러그인 의존성만 추가하면 됩니다.