ranGroupDetailModal.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. <template>
  2. <v-dialog
  3. v-model="isModal"
  4. persistent
  5. width="62.5rem"
  6. >
  7. <div class="v-common-dialog-wrapper custom-dialog alert">
  8. <div class="modal-tit">
  9. <strong>장비 현황</strong>
  10. <button
  11. class="btn-close"
  12. @click="fnClose"
  13. />
  14. </div>
  15. <div class="v-common-dialog-content pa-0">
  16. <div class="core--list--component">
  17. <h2 class="fw--500">
  18. 이벤트 현황
  19. </h2>
  20. <ul class="event--stat">
  21. <li class="critical">
  22. <i class="ico" /><span>CRITICAL</span><span>{{ neInfoItem.criCnt || 0 }}</span>
  23. </li>
  24. <li class="major">
  25. <i class="ico" /><span>MAJOR</span><span>{{ neInfoItem.majCnt || 0 }}</span>
  26. </li>
  27. <li class="minor">
  28. <i class="ico" /><span>MINOR</span><span>{{ neInfoItem.minCnt || 0 }}</span>
  29. </li>
  30. <li class="disconnected">
  31. <i class="ico" /><span>DISCONNECTED</span><span>0</span>
  32. </li>
  33. </ul>
  34. </div>
  35. <div class="core--list--component--grid mt--0">
  36. <div class="title">
  37. <h2>
  38. NE 그룹 목록 <span>({{ neGroupObj.count }}건)</span>
  39. </h2>
  40. </div>
  41. <div class="tbl-wrapper">
  42. <div class="tbl-wrap">
  43. <!-- ag grid -->
  44. <ag-grid-vue
  45. style="width:100%; height:calc(23vh);"
  46. class="ag-theme-quartz"
  47. :row-data="neGroupObj.list"
  48. :grid-options="neGroupGirdOptions"
  49. @grid-ready="fnOnNeGroupGirdReady"
  50. @row-clicked="fnGroupRowClick"
  51. />
  52. </div>
  53. </div>
  54. </div>
  55. <div class="core--list--component">
  56. <div class="map--area">
  57. <div
  58. v-if="neObj.neGroup"
  59. class="side--title"
  60. style="zIndex:10"
  61. >
  62. {{ neObj.neGroup }}
  63. </div>
  64. <!--맵 영역-->
  65. <div
  66. id="ran_detail_map"
  67. ref="refMap"
  68. style="height: 100%;"
  69. />
  70. </div>
  71. </div>
  72. <div class="core--list--component--grid mt--0">
  73. <div class="title">
  74. <h2 v-if="neObj.neGroup && neObj.count > 0">
  75. [{{ neObj.neGroup }}] NE 목록 <span>({{ neObj.count }}건)</span>
  76. </h2>
  77. <h2 v-else>
  78. NE 목록 <span>(0건)</span>
  79. </h2>
  80. </div>
  81. <div class="tbl-wrapper">
  82. <div class="tbl-wrap">
  83. <ag-grid-vue
  84. style="width:100%; height:22vh;"
  85. class="ag-theme-quartz"
  86. :grid-options="neGirdOptions"
  87. @grid-ready="fnOnNeGirdReady"
  88. />
  89. <!-- :row-data="neObj.neList" -->
  90. </div>
  91. </div>
  92. </div>
  93. </div>
  94. <div
  95. class="btn-wrap nw--btn--wrap"
  96. style="padding-top:1.88rem"
  97. >
  98. <div />
  99. <div class="inner--btn--wrap">
  100. <v-btn
  101. class="custom-btn btn-gray mini"
  102. @click="fnClose"
  103. >
  104. <i class="ico" />
  105. 닫기
  106. </v-btn>
  107. </div>
  108. </div>
  109. </div>
  110. </v-dialog>
  111. </template>
  112. <script setup>
  113. /***********************
  114. * import
  115. ************************/
  116. import "ag-grid-community/styles/ag-grid.css";
  117. import "ag-grid-community/styles/ag-theme-quartz.css";
  118. import { AgGridVue } from "ag-grid-vue3";
  119. import { useI18n } from "vue-i18n";
  120. import useUtil from "@/composables/useUtil";
  121. import { filename } from 'pathe/utils'
  122. import testJson from "@/components/home/dashboard/test.json"
  123. /***********************
  124. * plugins inject
  125. ************************/
  126. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp()
  127. // props
  128. const props = defineProps({
  129. tenantName: {
  130. type: String,
  131. required: true
  132. },
  133. })
  134. // 참조가능 데이터 설정
  135. defineExpose({})
  136. // 발신 이벤트 선언
  137. const emit = defineEmits(["closeModal"])
  138. const i18n = useI18n()
  139. /***********************
  140. * data & created
  141. ************************/
  142. const isModal = ref(true)
  143. const refMap = ref();
  144. const map = ref(null) // 카카오 맵 객체
  145. const markers = ref([]) // 카카오맵 마커(핀) 객체
  146. // 카카오맵 센터좌표
  147. const centerPosition = ref({
  148. lat: 33.450701,
  149. lng: 126.570667
  150. });
  151. const remToPx = () => parseFloat(getComputedStyle(document.documentElement).fontSize)
  152. const rowHeightRem = 2.5 // 원하는 rem 값
  153. const rowHeightPx = rowHeightRem * remToPx()
  154. const neGroupGridApi = shallowRef()
  155. const neGridApi = shallowRef()
  156. // Ran DetailInfo
  157. const neInfoItem = ref({});
  158. // neGroupObj
  159. const neGroupObj = ref({
  160. count: 0,
  161. list: []
  162. });
  163. // neObj
  164. const neObj = ref({
  165. count: 0,
  166. neGroup: '',
  167. list: []
  168. })
  169. // ne 그룹 목록 그리드 옵션
  170. const neGroupGirdOptions = {
  171. columnDefs: [
  172. { headerName: 'No', valueGetter: "node.rowIndex + 1", sortable: false, maxWidth: 100},
  173. { headerName: 'NE 그룹명', field: 'neGroup', sortable: false, minWidth: 300},
  174. { headerName: 'NE 수', field: 'neCnt', sortable: false,},
  175. { headerName: '알림', field: 'cls', sortable: false,cellRenderer: alarmCellRenderer}
  176. ],
  177. rowData: neGroupObj.value.list, // 테이블 데이터
  178. suppressMovableColumns: true,
  179. autoSizeStrategy: {
  180. type: "fitGridWidth", // width맞춤
  181. },
  182. headerHeight : rowHeightPx,
  183. rowHeight: rowHeightPx,
  184. pagination: true,
  185. suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
  186. suppressRowClickSelection: true, // 행 클릭 체크박스 무시
  187. localeText: {
  188. noRowsToShow: i18n.t('common.noData')
  189. },
  190. }
  191. // ne 목록 그리드 옵션
  192. const neGirdOptions = {
  193. defaultColDef: {
  194. lockVisible: true,
  195. },
  196. columnDefs: [
  197. { headerName: 'No', valueGetter: "node.rowIndex + 1", sortable: false, maxWidth: 100},
  198. { headerName: 'NE 이름', field: 'neName', sortable: false, cellRenderer: neNameCellRenderer,},
  199. { headerName: '이벤트', field: 'eventStatus', sortable: false, cellRenderer: eventCellRenderer, width: 250,}
  200. ],
  201. rowData: neObj.value.neList, // 테이블 데이터
  202. autoSizeStrategy: {
  203. type: "fitGridWidth", //fitCellContents
  204. },
  205. headerHeight : rowHeightPx,
  206. rowHeight: rowHeightPx,
  207. pagination: true,
  208. suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
  209. suppressRowClickSelection: true, // 행 클릭 체크박스 무시
  210. localeText: {
  211. noRowsToShow: 'NE 그룹을 선택하세요'
  212. },
  213. loading: false
  214. }
  215. /***********************
  216. * Methods
  217. ************************/
  218. const fnInit = async () => {
  219. await nextTick();
  220. // Ran 정보 조회
  221. await fnGeoTenantNeInfo();
  222. // 카카오 지도 load
  223. if (window.kakao && window.kakao.maps) {
  224. fnLoadKakakoMap()
  225. } else {
  226. fnAppendKakaoScript()
  227. }
  228. }
  229. function fnClose(){
  230. isModal.value = false
  231. setTimeout(() => {
  232. emit('closeModal')
  233. }, 250);
  234. }
  235. /**
  236. * P5G RAN Ne 정보 조회
  237. */
  238. function fnGeoTenantNeInfo() {
  239. return new Promise((resolve, reject) => {
  240. const params = {
  241. tenantName: props.tenantName
  242. }
  243. useAxios().post(useApi.tenantNeInfo, params).then((res) => {
  244. const {resCode, resMsg, data} = res.data;
  245. if(resCode == 200) {
  246. // 테스트를 위한 테스트 코드 (삭제 필수)
  247. // fnParseData([...(data?.items || []), ...testJson.ranNeGroupList.data.items])
  248. fnParseData(data.items || [])
  249. $log.debug("[tenantDashboard][fnGeoTenantNeInfo][success]")
  250. resolve();
  251. } else {
  252. $log.debug("[tenantDashboard][fnGeoTenantNeInfo][error]", `[${resCode}] ${resMsg}`);
  253. reject()
  254. }
  255. }).catch((error)=>{
  256. // 테스트를 위한 테스트 코드 (삭제 필수)
  257. // fnParseData([...testJson.ranNeGroupList.data.items])
  258. $log.debug("[tenantDashboard][fnGeoTenantNeInfo][error]")
  259. useErrorHandler().fnSetCommErrorHandle(error, fnGeoTenantNeInfo)
  260. reject()
  261. }).finally(()=>{
  262. $log.debug("[tenantDashboard][fnGeoTenantNeInfo][finished]")
  263. })
  264. })
  265. }
  266. const fnParseData = (items = []) => {
  267. // main > criCnt, majCnt, minCnt, neCnt, neGroupCnt, neGroupList
  268. // neGroupList > criCnt, majCnt, minCnt, neCnt, neGroup, tenantName, neList
  269. // neList > 모든 데이터
  270. // "tenantName": "SAMSUNGSDS",
  271. // "neGroup": "tenant01Group01",
  272. // "neName": "tenant01Group01ne02",
  273. // "neId": "CPC_100",
  274. // "neType": "UPF",
  275. // "upfNum": 10,
  276. // "customerType": 1,
  277. // "lastUpdateTime": "2024-09-10 00:00:00",
  278. // "neAddress": "서울시",
  279. // "neLocLatitude": "37.55203063",
  280. // "neLocLongitude": "126.98081697",
  281. // "familyName": "PUD",
  282. // "initTime": "2024-09-01 18:15:00",
  283. // "minCnt": 0,
  284. // "majCnt": 0,
  285. // "criCnt": 0,
  286. // "kpi": "{\"UL_RX_AVG_KBPS\":\"0.00\",\"UL_TX_AVG_KBPS\":\"0.00\",\"DL_RX_AVG_KBPS\":\"0.00\",\"DL_TX_AVG_KBPS\":\"0.00\"}",
  287. // "cpu": "{\"AVG_CPU_L\":\"10\",\"PEAK_CPU_L\":\"11\"}",
  288. // "mem": "{\"AVG_MEM_L\":\"17\",\"PEAK_MEM_L\":\"17\"}",
  289. // "areaCode": 11
  290. neInfoItem.value = {};
  291. if(!items || items.length < 1) {
  292. return;
  293. }
  294. // 테넌트 필터링
  295. const tenantNeItems = items.filter((i) => i.tenantName == props.tenantName);
  296. // const tenantNeItems = items;
  297. // 데이터 그룹핑
  298. let objInfo = {};
  299. objInfo = tenantNeItems.reduce((acc, curr) => {
  300. // 심각도 카운트 셋팅
  301. if(acc.criCnt) acc.criCnt = acc.criCnt + curr.criCnt;
  302. else acc.criCnt = curr.criCnt;
  303. if(acc.majCnt) acc.majCnt = acc.majCnt + curr.majCnt;
  304. else acc.majCnt = curr.majCnt;
  305. if(acc.minCnt) acc.minCnt = acc.minCnt + curr.minCnt;
  306. else acc.minCnt = curr.minCnt;
  307. if(!acc.neGroupList) acc.neGroupList = [];
  308. // ne 카운트 셋팅
  309. if(acc.neCnt) acc.neCnt = acc.neCnt + 1;
  310. else acc.neCnt = 1;
  311. // neGroupList 셋팅
  312. const groupIndex = acc.neGroupList.findIndex((g) => g.neGroup == curr.neGroup);
  313. if(groupIndex > -1) {
  314. acc.neGroupList[groupIndex].neCnt = acc.neGroupList[groupIndex].neCnt + 1;
  315. acc.neGroupList[groupIndex].criCnt = acc.neGroupList[groupIndex].criCnt + curr.criCnt;
  316. acc.neGroupList[groupIndex].majCnt = acc.neGroupList[groupIndex].majCnt + curr.majCnt;
  317. acc.neGroupList[groupIndex].minCnt = acc.neGroupList[groupIndex].minCnt + curr.minCnt;
  318. acc.neGroupList[groupIndex].neList.push(curr);
  319. } else {
  320. acc.neGroupList.push({
  321. tenantName: curr.tenantName,
  322. neGroup: curr.neGroup,
  323. criCnt: curr.criCnt,
  324. majCnt: curr.majCnt,
  325. minCnt: curr.minCnt,
  326. neCnt: 1,
  327. neList: [curr]
  328. })
  329. }
  330. //neGroup 카운트 셋팅
  331. if(acc.neGroupCnt) acc.neGroupCnt = acc.neGroupList?.length;
  332. else acc.neGroupCnt = 1;
  333. return acc;
  334. }, {})
  335. // 전체 목록 셋팅
  336. neInfoItem.value = objInfo;
  337. // NE 그룹 목록 셋팅
  338. neGroupObj.value.count = objInfo.neGroupList?.length;
  339. neGroupObj.value.list = objInfo.neGroupList;
  340. console.log('::::: neGroup 정보 셋팅 완료 :::::', neInfoItem.value)
  341. }
  342. /**
  343. * 카카오 지도 관련
  344. */
  345. // 카카오 지도 script 추가
  346. const fnAppendKakaoScript = () => {
  347. const script = document.createElement('script')
  348. script.async = true
  349. script.onload = () => {
  350. window.kakao.maps.load(fnLoadKakakoMap)
  351. }
  352. script.src = `//dapi.kakao.com/v2/maps/sdk.js?autoload=false&libraries=services,clusterer,drawing&appkey=${import.meta.env.VITE_APP_KAKAO_APP_KEY}`
  353. document.head.appendChild(script)
  354. }
  355. // 카카오 지도 Load
  356. const fnLoadKakakoMap = () => {
  357. const mapContainer = refMap.value;
  358. const mapOption = {
  359. center: new kakao.maps.LatLng(centerPosition.value.lat, centerPosition.value.lng), // 지도의 중심좌표
  360. level: 12, // 지도의 확대 레벨
  361. }
  362. map.value = new kakao.maps.Map(mapContainer, mapOption)
  363. }
  364. /** 마커 관련 */
  365. // 마커 이미지
  366. // 이미지 가져오기 (vite문법)
  367. const glob = import.meta.glob('~/assets/img/ico_*.{png,svg}', { eager: true })
  368. const getImages = Object.fromEntries(
  369. Object.entries(glob).map(([key, value]) => [filename(key), value.default])
  370. )
  371. const fnGetNePinIco = (item) => {
  372. const { criCnt, majCnt, minCnt } = item;
  373. if(criCnt && criCnt > 0) return 'red'
  374. else if(majCnt && majCnt > 0) return 'blue'
  375. else if(minCnt && minCnt > 0) return 'black'
  376. else return 'gray'
  377. }
  378. // 마커 생성
  379. const fnCreateMaker = (item, position, index) => {
  380. const markerImageSrc = getImages[`ico_${fnGetNePinIco(item)}_pin`]; // assets 폴더의 이미지 경로
  381. const markerImageSize = new kakao.maps.Size(40, 40); // 마커 이미지 사이즈
  382. const markerImage = new kakao.maps.MarkerImage(markerImageSrc, markerImageSize);
  383. const markerOption = {
  384. map: map.value, // 마커를 표시할 지도
  385. position: position, // 마커의 위치
  386. clickable: true,
  387. image: markerImage,
  388. zIndex: 1,
  389. }
  390. return new kakao.maps.Marker(markerOption);
  391. }
  392. // 마커 그리기
  393. const fnDrawMaker = () => {
  394. // 마커 초기화
  395. fnClearMarker();
  396. // 마커 생성
  397. neObj.value.neList.forEach((item, index) => {
  398. const position = new kakao.maps.LatLng(item.neLocLatitude, item.neLocLongitude)
  399. const marker = fnCreateMaker(item, position, index);
  400. markers.value.push({
  401. overlay: null,
  402. markers: marker,
  403. data: item,
  404. click: false,
  405. position: position
  406. })
  407. // 마커 클릭 이벤트등록
  408. kakao.maps.event.addListener(marker, 'click', fnClickMarkerEvent(index))
  409. // 첫번째 마커로 이동
  410. if(index == 0) map.value.panTo(position)
  411. })
  412. }
  413. // 마커 초기화
  414. const fnClearMarker = () => {
  415. markers.value.forEach(marker => marker.markers.setMap(null));
  416. fnClearMarkerOverlay()
  417. markers.value = []
  418. }
  419. // 마커 click 이벤트 등록
  420. function fnClickMarkerEvent(index) {
  421. const marker = markers.value[index];
  422. const openOverlay = () => {
  423. fnClickMaker(marker)
  424. fnOpenOverlay(marker)
  425. }
  426. return openOverlay;
  427. }
  428. // 실제 마커 클릭시 동작하는 함수
  429. const fnClickMaker = (marker) => {
  430. fnClearMakerClick(); // 마커 클릭 초기화
  431. marker.click = true;
  432. marker.markers.setZIndex(3)
  433. map.value.panTo(marker.position);
  434. }
  435. // 마커 Click 초기화
  436. const fnClearMakerClick = () => {
  437. markers.value.forEach((item) => {
  438. item.click = false;
  439. item.markers.setZIndex(2);
  440. })
  441. }
  442. /** 오버레이 윈도우 관련 */
  443. // 오버레이 open
  444. const fnOpenOverlay = (marker) => {
  445. // 모든 오버레이 초기화
  446. fnClearMarkerOverlay();
  447. // 오버레이 생성
  448. marker.overlay = fnCreateCustomOverlay(marker);
  449. // 오버레이 zindex 설정
  450. marker.overlay.setZIndex(999);
  451. // 오버레이 지도에 적용
  452. marker.overlay.setMap(map.value);
  453. // 오버레이 닫기 버튼
  454. const closeBtn = document.querySelector('#overlayCloseBtn')
  455. closeBtn.addEventListener('click', fnCloseOverlay)
  456. }
  457. // 오버레이 생성
  458. const fnCreateCustomOverlay = (marker) => {
  459. //kpi 정보 element 생성
  460. const kpiEls = (fnGetKPIInfo(marker?.data) || {}).map((kpi)=> {
  461. return `<li><i class="ico ${kpi.ico}"></i><span>${kpi.label}</span><span class="">${parseFloat(kpi.val|| 0).toFixed(2)}%</span></li>`
  462. });
  463. // custom overlay html 생성
  464. const contents = ` <div class="area--info" style="top: -10.7rem; right: -6rem;">
  465. <div class="area--info--title">
  466. <p style="overflow:hidden; text-overflow: ellipsis;">${marker?.data?.neName}</p>
  467. <button class="btn-close" id="overlayCloseBtn"></button>
  468. </div>
  469. <ul>
  470. <li><i class="ico green"></i><span>STATUS</span><span class="active">ACTIVE</span></li>
  471. ${kpiEls.join('')}
  472. </ul>
  473. </div>`;
  474. // overlay 생성
  475. return new kakao.maps.CustomOverlay({
  476. position: marker.position,
  477. content: contents
  478. })
  479. }
  480. // 오버레이에 표시할 KPI 정보 생성
  481. const fnGetKPIInfo = (data) => {
  482. // KPI 정보 셋팅 - TODO: CPU, MEM 외의 값이 추가되면 여기에 추가
  483. const { cpu, mem } = data;
  484. const kpiItems = [];
  485. const getBodyLevelClass = (val) => {
  486. if(val >= 95) return 'red'; // red
  487. else if(val >= 90) return 'blue'; // blue
  488. else if(val >= 85) return 'gray'; // gray
  489. else return 'green'; // green
  490. }
  491. if(cpu) {
  492. const objCpu = typeof cpu === 'string' ? JSON.parse(cpu) : cpu;
  493. if(objCpu && objCpu[`AVG_CPU_L`]){
  494. kpiItems.push({label: 'CPU', val: objCpu[`AVG_CPU_L`] || 0, ico: getBodyLevelClass(objCpu[`AVG_CPU_L`])})
  495. }
  496. }
  497. if(mem) {
  498. const objMem = typeof mem === 'string' ? JSON.parse(mem) : mem ;
  499. if(objMem && objMem['AVG_MEM_L']){
  500. kpiItems.push({label: 'MEMORY', val: objMem['AVG_MEM_L'] || 0, ico: getBodyLevelClass(objMem[`AVG_MEM_L`])})
  501. }
  502. }
  503. return kpiItems;
  504. }
  505. // 오버레이 닫기 이벤트
  506. const fnCloseOverlay = () => {
  507. fnClearMarkerOverlay();
  508. fnClearMakerClick();
  509. }
  510. // 모든 마커 오버레이 초기화
  511. const fnClearMarkerOverlay = () => {
  512. markers.value.forEach((marker) => {
  513. if(marker.overlay) {
  514. marker.overlay.setMap(null);
  515. marker.overlay = null;
  516. }
  517. })
  518. }
  519. /**
  520. * NE 그룹 목록 그리드 관련
  521. */
  522. function fnOnNeGroupGirdReady(params) {
  523. neGroupGridApi.value = params.api
  524. }
  525. // 알림 Cell 렌더러
  526. function alarmCellRenderer(params) {
  527. let _cls = "";
  528. if(params.data.criCnt > 0){
  529. _cls = 'alarm--red';
  530. } else if(params.data.majCnt > 0){
  531. _cls = 'alarm--blue';
  532. } else if(params.data.minCnt > 0){
  533. _cls = 'alarm--gray'
  534. }
  535. return `<span class="${_cls}"></span>`;
  536. }
  537. //row clikc
  538. function fnGroupRowClick(params) {
  539. const { neList, neGroup } = params.data
  540. neObj.value.count = neList.length;
  541. neObj.value.neList = neList;
  542. // neObj.value.neList = neList.sort((a, b) => {
  543. // return a.criCnt < b.criCnt ? 1 : a.criCnt > b.criCnt ? -1 : (a.majCnt < b.majCnt ? 1 : a.majCnt > b.majCnt ? -1 : (a.minCnt < b.minCnt ? 1 : a.minCnt > b.minCnt ? -1 : 0));
  544. // });
  545. neObj.value.neGroup = neGroup;
  546. // 그리드
  547. neGridApi.value.setGridOption('rowData', neObj.value.neList)
  548. // 지도에 마커 생성
  549. fnDrawMaker();
  550. }
  551. /**
  552. * NE 목록 그리드 관련
  553. */
  554. function fnOnNeGirdReady(params) {
  555. neGridApi.value = params.api
  556. }
  557. // Ne 이름 Cell 렌더러
  558. function neNameCellRenderer(params) {
  559. let _cls = "";
  560. if(params.data.criCnt > 0){
  561. _cls = 'pin--red';
  562. } else if(params.data.majCnt > 0){
  563. _cls = 'pin--blue';
  564. } else if(params.data.minCnt > 0){
  565. _cls = 'pin--black'
  566. } else {
  567. _cls = 'pin--gray'
  568. }
  569. return `<span class="${_cls}">${params.data.neName}</span>`;
  570. }
  571. // 이벤트 Cell 렌더러
  572. function eventCellRenderer(params) {
  573. const { criCnt, majCnt, minCnt } = params.data;
  574. const events = [];
  575. if(criCnt && criCnt > 0) {
  576. events.push(`<span class="evt--critical">CRITICAL (${criCnt}) </span>`)
  577. }
  578. if(majCnt && majCnt > 0) {
  579. events.push(`<span class="evt--major">MAJOR (${majCnt}) </span>`)
  580. }
  581. if(minCnt && minCnt > 0) {
  582. events.push(`<span class="evt--minor">MINOR (${minCnt}) </span>`)
  583. }
  584. if(events.length > 0) {
  585. return events.join('');
  586. } else {
  587. return `<span class="evt--none">-</span>`;
  588. }
  589. }
  590. onMounted(() => fnInit())
  591. </script>