diff --git a/build.gradle b/build.gradle index 58a5dd2c..60f88500 100644 --- a/build.gradle +++ b/build.gradle @@ -1,79 +1,77 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.4.4' - id 'io.spring.dependency-management' version '1.1.7' + id 'java' + id 'org.springframework.boot' version '3.4.4' + id 'io.spring.dependency-management' version '1.1.7' } group = 'com.factoreal' version = '0.0.1-SNAPSHOT' java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' - - // MySQL Connector - runtimeOnly 'com.mysql:mysql-connector-j' - // H2 Database - // runtimeOnly 'com.h2database:h2' - - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - // MQTT 메세지 송수신을 위한 의존성 추가 - implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5' - implementation 'org.bouncycastle:bcprov-jdk15on:1.70' - implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' - - // swagger doc - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' - - // flyway db - implementation 'org.flywaydb:flyway-core' - implementation 'org.flywaydb:flyway-mysql' - - // AWS SDK for IoT Data Plane - implementation platform('software.amazon.awssdk:bom:2.25.19') - implementation 'software.amazon.awssdk:secretsmanager' - implementation 'software.amazon.awssdk:regions' - implementation 'software.amazon.awssdk:iotdataplane' - implementation 'software.amazon.awssdk:sns' - - // AWS 테스트용 LocalStack - testImplementation 'org.testcontainers:junit-jupiter' - testImplementation 'org.testcontainers:localstack' - testImplementation 'software.amazon.awssdk:sns' - - // Websocket - implementation("org.springframework.boot:spring-boot-starter-websocket:3.4.5") - - // kafka - implementation 'org.springframework.kafka:spring-kafka' - testImplementation 'org.springframework.kafka:spring-kafka-test' - - // WebSocket - implementation 'org.springframework.boot:spring-boot-starter-websocket' - - // ELK - implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.17.21' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + + // MySQL Connector + runtimeOnly 'com.mysql:mysql-connector-j' + // H2 Database + // runtimeOnly 'com.h2database:h2' + + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // MQTT 메세지 송수신을 위한 의존성 추가 + implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5' + implementation 'org.bouncycastle:bcprov-jdk15on:1.70' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' + + // swagger doc + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' + + // flyway db + implementation 'org.flywaydb:flyway-core' + implementation 'org.flywaydb:flyway-mysql' + + // AWS SDK for IoT Data Plane + implementation platform('software.amazon.awssdk:bom:2.25.19') + implementation 'software.amazon.awssdk:secretsmanager' + implementation 'software.amazon.awssdk:regions' + implementation 'software.amazon.awssdk:iotdataplane' + implementation 'software.amazon.awssdk:sns' + + // AWS 테스트용 LocalStack + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:localstack' + testImplementation 'software.amazon.awssdk:sns' + + // Websocket + implementation("org.springframework.boot:spring-boot-starter-websocket:3.4.5") + + // kafka + implementation 'org.springframework.kafka:spring-kafka' + testImplementation 'org.springframework.kafka:spring-kafka-test' + + // WebSocket + implementation 'org.springframework.boot:spring-boot-starter-websocket' } + tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/elk-compose.yml b/elk-compose.yml deleted file mode 100644 index 8346a12f..00000000 --- a/elk-compose.yml +++ /dev/null @@ -1,33 +0,0 @@ -services: - elasticsearch: -# image: docker.elastic.co/elasticsearch/elasticsearch:7.17.21 - image: juneshyoo/es-kibana:cleaned # push 한 이미지 - # image: juneshyoo/es-with-kibana-data:latest # ✅ 네가 push한 이미지 사용 - container_name: elasticsearch - environment: - - discovery.type=single-node - - xpack.security.enabled=false # 인증 비활성화 (개발용) - - ES_JAVA_OPTS=-Xms512m -Xmx512m # 💥 힙 메모리 줄이기 - ports: - - "9200:9200" # Elasticsearch API -# volumes: -# - es-data:/usr/share/elasticsearch/data - - kibana: -# image: docker.elastic.co/kibana/kibana:7.17.21 - image: juneshyoo/kibana-objects:cleaned - container_name: kibana - ports: - - "5601:5601" # Kibana UI - environment: - - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 - depends_on: - - elasticsearch -# volumes: -# - ./kibana.yml:/usr/share/kibana/config/kibana.yml # ✅ 커스텀 설정 적용 - - -#volumes: -# es-data: -# kibana-data: - diff --git a/src/main/java/com/factoreal/backend/domain/sensor/dao/SensorRepository.java b/src/main/java/com/factoreal/backend/domain/sensor/dao/SensorRepository.java index 66997b5d..99ec602d 100644 --- a/src/main/java/com/factoreal/backend/domain/sensor/dao/SensorRepository.java +++ b/src/main/java/com/factoreal/backend/domain/sensor/dao/SensorRepository.java @@ -11,4 +11,6 @@ public interface SensorRepository extends JpaRepository { List findByZone(Zone zone); + + List findByZone_ZoneId(String zoneId); } \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/global/config/ElasticsearchConfig.java b/src/main/java/com/factoreal/backend/global/config/ElasticsearchConfig.java deleted file mode 100644 index c31a1ea5..00000000 --- a/src/main/java/com/factoreal/backend/global/config/ElasticsearchConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.factoreal.backend.global.config; - -import org.apache.http.HttpHost; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestHighLevelClient; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.beans.factory.annotation.Value; - -@Configuration -public class ElasticsearchConfig { - - @Value("${elasticsearch.host}") - private String esHost; - @Value("${elasticsearch.port}") - private int esPort; - @Value("${elasticsearch.scheme}") - private String esScheme; - - @Bean - public RestHighLevelClient elasticsearchClient() { - // Build ES client using properties - return new RestHighLevelClient( - RestClient.builder( - new HttpHost(esHost, esPort, esScheme) - ) - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/messaging/grafana/DashboardFactory.java b/src/main/java/com/factoreal/backend/messaging/grafana/DashboardFactory.java new file mode 100644 index 00000000..73c323dc --- /dev/null +++ b/src/main/java/com/factoreal/backend/messaging/grafana/DashboardFactory.java @@ -0,0 +1,94 @@ +package com.factoreal.backend.messaging.grafana; + +import com.factoreal.backend.domain.sensor.entity.Sensor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.UUID; + +@Component +public class DashboardFactory { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * 센서별 패널을 포함한 대시보드 JSON 반환 + */ + public String build(String zoneId, + List sensors, + String datasourceUid) throws JsonProcessingException { + + // 루트 JSON 객체 + ObjectNode root = MAPPER.createObjectNode(); + ObjectNode dash = root.putObject("dashboard"); + + // 대시보드 메타 + dash.put("uid", UUID.randomUUID().toString().substring(0, 12)); + dash.put("title", zoneId); + dash.put("timezone", "browser"); + + // 자동 데이터 갱신: 최근 15분의 시계열을 2초마다 갱신합니다 + dash.set("time", MAPPER.readTree("{\"from\":\"now-15m\",\"to\":\"now\"}")); + dash.put("refresh", "2s"); + + // panels 배열 + ArrayNode panels = dash.putArray("panels"); + + // 센서별 패널 생성 + for (int i = 0; i < sensors.size(); i++) { + String sensorId = sensors.get(i).getSensorId(); + + // 패널 객체 + ObjectNode panel = MAPPER.createObjectNode(); + panel.put("id", i + 1); + panel.put("type", "timeseries"); + panel.put("title", sensorId + " (Last 15min)"); + panel.set("gridPos", MAPPER.readTree( + String.format("{\"x\": %d, \"y\": 0, \"w\": 12, \"h\": 8}", + (i % 2) * 12) // 좌우 2열 배치 + )); + panel.put("datasource", datasourceUid); + + // targets 배열 + ArrayNode targets = panel.putArray("targets"); + ObjectNode tgt = targets.addObject(); + tgt.put("refId", "A"); + tgt.put("measurement", "ENVIRONMENT"); + tgt.put("policy", "default"); + tgt.put("resultFormat", "time_series"); + tgt.put("orderByTime", "ASC"); + + // SELECT clause: raw field(val) + ArrayNode select = tgt.putArray("select"); + ArrayNode selectClause = select.addArray(); + + ObjectNode fieldStep = MAPPER.createObjectNode(); + fieldStep.put("type", "field"); + fieldStep.putArray("params").add("val"); + selectClause.add(fieldStep); + + // Tag filter: sensor = current sensorId + ArrayNode tags = tgt.putArray("tags"); + ObjectNode tagFilter = MAPPER.createObjectNode(); + tagFilter.put("key", "sensorId::field"); + tagFilter.put("operator", "="); + tagFilter.put("value", sensorId); + tags.add(tagFilter); + + tgt.put("datasource", datasourceUid); + tgt.put("hide", false); + + panels.add(panel); + } + + // 기타 옵션 + root.put("folderId", 0); + root.put("overwrite", false); + + return MAPPER.writeValueAsString(root); + } +} diff --git a/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaClient.java b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaClient.java new file mode 100644 index 00000000..d993865d --- /dev/null +++ b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaClient.java @@ -0,0 +1,60 @@ +package com.factoreal.backend.messaging.grafana; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; + +@Slf4j +@Component +@RequiredArgsConstructor +public class GrafanaClient { + + private final RestTemplateBuilder restTemplateBuilder; + + @Value("${grafana.url}") + private String url; + + @Value("${grafana.api-key}") + private String apiKey; + + private RestTemplate rest() { + return restTemplateBuilder.rootUri(url).build(); + } + + public String createDashboard(String dashboardJson) { + RestTemplate restTemplate = rest(); + + // 1) 헤더 설정 + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(apiKey); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 2) HttpEntity에 payload + headers 담기 + HttpEntity entity = new HttpEntity<>(dashboardJson, headers); + + // 3) POST 요청 + ResponseEntity> res = restTemplate.exchange( + "/api/dashboards/db", + HttpMethod.POST, + entity, + new ParameterizedTypeReference<>() { + } + ); + + Map body = res.getBody(); + if (body == null || body.get("uid") == null) { + log.error("Grafana API 대시보드 생성 실패: {}", body); + throw new IllegalArgumentException("Cannot create dashboard"); + } + + return body.get("uid").toString(); + } + +} diff --git a/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaSensorResponseDto.java b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaSensorResponseDto.java new file mode 100644 index 00000000..4ba7ecd6 --- /dev/null +++ b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaSensorResponseDto.java @@ -0,0 +1,12 @@ +package com.factoreal.backend.messaging.grafana; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GrafanaSensorResponseDto { + private final String sensorId; + private final String sensorType; + private final String iframeUrl; +} diff --git a/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaZoneController.java b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaZoneController.java new file mode 100644 index 00000000..ffa9488b --- /dev/null +++ b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaZoneController.java @@ -0,0 +1,22 @@ +package com.factoreal.backend.messaging.grafana; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/grafana-zone") +public class GrafanaZoneController { + private final GrafanaZoneService grafanaZoneService; + + @GetMapping("/{zoneId}/dashboards") + public List getDashboards(@PathVariable String zoneId) throws JsonProcessingException { + return grafanaZoneService.createDashboardUrls(zoneId); + } +} diff --git a/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaZoneService.java b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaZoneService.java new file mode 100644 index 00000000..fd76682d --- /dev/null +++ b/src/main/java/com/factoreal/backend/messaging/grafana/GrafanaZoneService.java @@ -0,0 +1,67 @@ +package com.factoreal.backend.messaging.grafana; + +import com.factoreal.backend.domain.sensor.dao.SensorRepository; +import com.factoreal.backend.domain.sensor.entity.Sensor; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class GrafanaZoneService { + private final SensorRepository sensorRepository; + private final DashboardFactory dashboardFactory; + private final GrafanaClient grafanaClient; + + @Value("${grafana.url}") + private String grafanaUrl; + + @Value("${grafana.org-id}") + private int orgId; + + @Value("${grafana.datasource-uid}") + private String datasourceUid; + + // 30 분 임계 + private static final Duration THRESHOLD = Duration.ofMinutes(30); + + /** + * zoneId용 대시보드를 1개 생성하고, + * (sensorName → panel별 iframe URL) 맵을 반환 + */ + public List createDashboardUrls(String zoneId) throws JsonProcessingException { + // 1) 센서 목록 조회 + List sensors = sensorRepository.findByZone_ZoneId(zoneId); + if (sensors.isEmpty()) { + throw new IllegalStateException("No sensors for zone: " + zoneId); + } + + // 2) 기존 대시보드 검색 : TODO + + // 3) 대시보드 JSON (센서 패널 다 포함) + String json = dashboardFactory.build(zoneId, sensors, datasourceUid); + + // 4) Grafana에 대시보드 1개 생성 + String dashboardUid = grafanaClient.createDashboard(json); + + // 5) 센서 이름 → iframe URL 매핑 (panelId = 인덱스+1) + List responses = new ArrayList<>(); + for (int i = 0; i < sensors.size(); i++) { + String sensorId = sensors.get(i).getSensorId(); + String sensorType = sensors.get(i).getSensorType().toString().toUpperCase(); + int panelId = i + 1; + + String iframeUrl = String.format( + "%s/d-solo/%s/%s?orgId=%d&panelId=%d&kiosk=tv&from=now-1h&to=now", + grafanaUrl, dashboardUid, sensorId, orgId, panelId + ); + responses.add(new GrafanaSensorResponseDto(sensorId, sensorType, iframeUrl)); + } + return responses; + } +} diff --git a/src/main/java/com/factoreal/backend/messaging/kafka/KafkaConsumerD.java b/src/main/java/com/factoreal/backend/messaging/kafka/KafkaConsumerD.java index ffd2ea11..a7427e1c 100644 --- a/src/main/java/com/factoreal/backend/messaging/kafka/KafkaConsumerD.java +++ b/src/main/java/com/factoreal/backend/messaging/kafka/KafkaConsumerD.java @@ -13,18 +13,14 @@ import com.factoreal.backend.messaging.kafka.strategy.enums.AlarmEventDto; import com.factoreal.backend.messaging.kafka.strategy.enums.RiskLevel; import com.factoreal.backend.messaging.kafka.strategy.enums.SensorType; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; -import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import com.factoreal.backend.domain.sensor.application.SensorService; import com.factoreal.backend.domain.sensor.entity.Sensor; + import java.time.ZonedDateTime; import java.time.ZoneId; @@ -50,13 +46,6 @@ public class KafkaConsumerD { // 공간(zone)별로 마지막 위험도 저장하기 위한 Map (초기에는 위험도 -1) private static final Map lastDangerLevelMap = new ConcurrentHashMap<>(); - // ELK - private final RestHighLevelClient elasticsearchClient; // ELK client - - // Elasticsearch index name from configuration - @Value("${elasticsearch.index}") - private String esIndex; - // 로그 기록용 private final AbnormalLogService abnormalLogService; @@ -77,11 +66,7 @@ public void consume(String message) { // 공간 센서일 때만 히트맵용 웹소켓 전송 if (dto.getEquipId() != null && dto.getZoneId() != null && dto.getEquipId().equals(dto.getZoneId())) { - log.info("✅ 공산 센서 로직 start"); - // ################################# - // 비동기 ES 저장 - // ################################# - saveToElasticsearch(dto); + log.info("✅ 공간 센서 로직 start"); log.info("▶︎ 위험도 감지 start"); int dangerLevel = getDangerLevel(dto.getSensorType(), dto.getVal()); @@ -126,23 +111,6 @@ public void consume(String message) { } - // ✅ Elastic 비동기 저장 - @Async - public void saveToElasticsearch(SensorKafkaDto dto) { - try { - Map map = objectMapper.convertValue(dto, new TypeReference<>() { - }); - map.put("timestamp", Instant.now().toString()); // 타임필드 추가 - - IndexRequest request = new IndexRequest(esIndex).source(map); - elasticsearchClient.index(request, RequestOptions.DEFAULT); - - log.info("✅ Elasticsearch 저장 완료: {}", dto.getSensorId()); - } catch (Exception e) { - log.error("❌ Elasticsearch 저장 실패: {}", dto, e); - } - } - @Async public void startAlarm(SensorKafkaDto sensorData, AbnormalLog abnormalLog, RiskLevel riskLevel) { AlarmEventDto alarmEventDto; @@ -195,11 +163,11 @@ public void sendSystemLog(SensorKafkaDto dto) { .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); SystemLogDto logDto = new SystemLogDto( - zoneId, zoneName, - dto.getSensorType(), - newLevel, - dto.getVal(), // 이 부분 추가 - timestamp); + zoneId, zoneName, + dto.getSensorType(), + newLevel, + dto.getVal(), // 이 부분 추가 + timestamp); webSocketSender.sendSystemLog(logDto); } @@ -329,26 +297,20 @@ private void performAutoControl(SensorKafkaDto dto) { } private String buildControlMessage( // 제어 로직 - String type, double val, double thresh, double tol) { + String type, double val, double thresh, double tol) { return switch (type.toLowerCase()) { - case "temp" -> - String.format("현재 온도는 %.1f℃입니다. 적정 온도 범위는 %.1f~%.1f℃입니다.", - val, thresh - tol, thresh + tol); - case "humid" -> - String.format("현재 습도는 %.1f%%입니다. 적정 습도 범위는 %.1f~%.1f%%입니다.", - val, thresh - tol, thresh + tol); - case "vibration" -> - String.format("현재 진동 값은 %.1fmm/s입니다. 허용 범위는 %.1f~%.1fmm/s입니다.", - val, thresh - tol, thresh + tol); - case "current" -> - String.format("현재 전류는 %.1fmA입니다. 허용 범위는 %.1f~%.1fmA입니다.", - val, thresh - tol, thresh + tol); - case "dust" -> - String.format("현재 미세먼지는 %.1f㎍/㎥입니다. 허용 범위는 %.1f~%.1f㎍/㎥입니다.", - val, thresh - tol, thresh + tol); - default -> - String.format("현재 값은 %.1f이고, 허용 범위는 %.1f~%.1f입니다.", - val, thresh - tol, thresh + tol); + case "temp" -> String.format("현재 온도는 %.1f℃입니다. 적정 온도 범위는 %.1f~%.1f℃입니다.", + val, thresh - tol, thresh + tol); + case "humid" -> String.format("현재 습도는 %.1f%%입니다. 적정 습도 범위는 %.1f~%.1f%%입니다.", + val, thresh - tol, thresh + tol); + case "vibration" -> String.format("현재 진동 값은 %.1fmm/s입니다. 허용 범위는 %.1f~%.1fmm/s입니다.", + val, thresh - tol, thresh + tol); + case "current" -> String.format("현재 전류는 %.1fmA입니다. 허용 범위는 %.1f~%.1fmA입니다.", + val, thresh - tol, thresh + tol); + case "dust" -> String.format("현재 미세먼지는 %.1f㎍/㎥입니다. 허용 범위는 %.1f~%.1f㎍/㎥입니다.", + val, thresh - tol, thresh + tol); + default -> String.format("현재 값은 %.1f이고, 허용 범위는 %.1f~%.1f입니다.", + val, thresh - tol, thresh + tol); }; } diff --git a/src/main/resources/application-cloud.yml b/src/main/resources/application-cloud.yml index c4206389..8b54b51a 100644 --- a/src/main/resources/application-cloud.yml +++ b/src/main/resources/application-cloud.yml @@ -28,9 +28,8 @@ aws: secret-key: ${AWS_IAM_SECRET_KEY} region: ap-northeast-2 - -elasticsearch: - host: elasticsearch - port: 9200 - scheme: http - index: sensor-data \ No newline at end of file +grafana: + url: ${GRAFANA_URL} + api-key: ${GRAFANA_API_KEY} + org-id: 1 + datasource-uid: ${GRAFANA_DATASOURCE_UID} \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index f1aef477..8d3b7060 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -29,9 +29,8 @@ aws: secret-key: ${AWS_IAM_SECRET_KEY} region: ap-northeast-2 -# Elasticsearch client settings -elasticsearch: - host: localhost - port: 9200 - scheme: http - index: sensor-data \ No newline at end of file +grafana: + url: ${GRAFANA_URL} + api-key: ${GRAFANA_API_KEY} + org-id: 1 + datasource-uid: ${GRAFANA_DATASOURCE_UID} \ No newline at end of file