블로그 포스팅자동화진행중

This commit is contained in:
hehihoho3@gmail.com 2025-08-19 12:51:17 +09:00
parent 2d8955144b
commit d3bd8fe3ec
20 changed files with 261 additions and 131 deletions

View File

@ -1,19 +1,15 @@
package com.itn.admin.cmn.config;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@ -24,6 +20,7 @@ import javax.sql.DataSource;
,"com.itn.admin.itn.mjon.spam.mapper"
,"com.itn.admin.itn.user.mapper"
,"com.itn.admin.itn.code.mapper"
,"com.itn.admin.itn.blog.mapper"
}
, sqlSessionFactoryRef = "factory")
class MainDatabaseConfig {
@ -47,7 +44,8 @@ class MainDatabaseConfig {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
sqlSessionFactory.setTypeAliasesPackage("com.itn.admin.itn.*");
// sqlSessionFactory.setTypeAliasesPackage("com.itn.admin.itn.*");
// sqlSessionFactory.setTypeAliasesPackage("com.itn.admin.itn");
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/itn/**/*Mapper.xml"));
sqlSessionFactory.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
return sqlSessionFactory.getObject();

View File

@ -28,6 +28,7 @@ public class UserInterceptor implements HandlerInterceptor {
log.info(" :: Request URL: " + request.getRequestURL());
log.info(" :: Request Method: " + request.getMethod());
log.info(" :: Remote Address: " + request.getRemoteAddr());
log.info(" :: modelAndView: " + modelAndView);
if (modelAndView != null) {

View File

@ -1,14 +1,20 @@
package com.itn.admin.cmn.util.slack;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.json.simple.JSONObject;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 레거시 Slack 유틸리티 클래스
*
* @deprecated 새로운 프로젝트에서는 {@link SlackNotificationService} 사용하세요.
* 기존 코드 호환성을 위해 유지됩니다.
*
* packageName : com.itn.mjonApi.util.slack
* fileName : Slack
* author : hylee
@ -18,40 +24,74 @@ import java.io.IOException;
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2023-08-28 hylee 최초 생성
* 2025-01-16 claude SlackNotificationService 통합을 위한 리팩토링
*/
@Slf4j
@Component
@Deprecated
public class SlackUtil {
/**
* 모락 메뉴 알림 발송 (레거시 메서드)
* @deprecated 새로운 알림은 {@link SlackNotificationService#sendCustomMessage} 사용하세요.
*/
@Deprecated
public static void sendMorakMenuToSlack(String sendMsg) {
String url = "https://hooks.slack.com/services/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";
sendSlackMessage(url, "모락메뉴api", sendMsg, "모락 메뉴", ":bento:");
}
/**
* 범용 슬랙 메시지 발송 메서드
* @param webhookUrl 웹훅 URL
* @param channel 채널명
* @param message 메시지 내용
* @param username 발송자명
* @param iconEmoji 아이콘 이모지
*/
public static void sendSlackMessage(String webhookUrl, String channel, String message,
String username, String iconEmoji) {
HttpClient client = new HttpClient();
PostMethod post = new PostMethod(url);
PostMethod post = new PostMethod(webhookUrl);
JSONObject json = new JSONObject();
try {
String munjaText = sendMsg;
json.put("channel", "모락메뉴api");
json.put("text", munjaText);
// json.put("icon_emoji", ":원하는 아이콘:"); //커스터마이징으로 아이콘 만들수도 있다!
json.put("username", "모락 메뉴");
json.put("channel", channel);
json.put("text", message);
json.put("username", username);
if (iconEmoji != null && !iconEmoji.trim().isEmpty()) {
json.put("icon_emoji", iconEmoji);
}
post.addParameter("payload", json.toString());
// 처음에 utf-8로 content-type안넣어주니까 한글은 깨져서 content-type넣어줌
post.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
int responseCode = client.executeMethod(post);
String response = post.getResponseBodyAsString();
if (responseCode != HttpStatus.SC_OK) {
System.out.println("Response: " + response);
if (responseCode == HttpStatus.SC_OK) {
log.debug("Slack 메시지 발송 성공: {} to {}", message, channel);
} else {
log.warn("Slack 메시지 발송 실패 - Code: {}, Response: {}", responseCode, response);
}
} catch (IllegalArgumentException e) {
System.out.println("IllegalArgumentException posting to Slack " + e);
log.error("Slack 발송 중 잘못된 인자 오류: {}", e.getMessage(), e);
} catch (IOException e) {
System.out.println("IOException posting to Slack " + e);
log.error("Slack 발송 중 IO 오류: {}", e.getMessage(), e);
} catch (Exception e) {
log.error("Slack 발송 중 예상치 못한 오류: {}", e.getMessage(), e);
} finally {
post.releaseConnection();
}
}
/**
* 단순 메시지 발송 (기본 설정 사용)
* @param webhookUrl 웹훅 URL
* @param message 메시지 내용
*/
public static void sendSimpleMessage(String webhookUrl, String message) {
sendSlackMessage(webhookUrl, "general", message, "ITN-Admin", ":robot_face:");
}
}

View File

@ -3,6 +3,8 @@ package com.itn.admin.cmn.vo;
import lombok.*;
import lombok.experimental.SuperBuilder;
import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@ -19,10 +21,14 @@ public class CmnVO {
private int totalPageCount; // 페이지
private int startPage; // 페이지네이션의 시작 페이지 번호
private int endPage; // 페이지네이션의 페이지 번호
private String frstRegisterId;
private String frstRegistPnttm;
private String lastUpdusrId;
private String lastUpdtPnttm;
private String createdBy;
private LocalDateTime frstRegistPnttm;
private String updatedBy;
private LocalDateTime lastUpdtPnttm;
// 등록자/수정자 정보
private String frstRegisterId; // 최초 등록자 ID
private String lastUpdusrId; // 최종 수정자 ID
// 페이징을 위한 offset과 limit을 계산하는 메서드

View File

@ -213,7 +213,7 @@ public class CommuteServiceImpl implements CommuteService {
// 활동 시간이 기준 시간보다 이전이면 조기 퇴근
if (activityTime.isBefore(checkTime)) {
return "70"; //""조기퇴근";
return "90"; //""조기퇴근";
}
// 그렇지 않으면 문자열 반환
return "";
@ -228,7 +228,7 @@ public class CommuteServiceImpl implements CommuteService {
// 활동 시간이 기준 시간보다 이후면 지각
if (activityTime.isAfter(checkTime)) {
return "60"; // 지각 60 반환 (공통코드 COMMUTE)
return "70"; // 지각 70 반환 (공통코드 COMMUTE)
}
// 그렇지 않으면 문자열 반환
return "";

View File

@ -0,0 +1,31 @@
package com.itn.admin.itn.blog.service;
import com.itn.admin.itn.blog.mapper.domain.BlogAccountWithSourcesDTO;
import com.itn.admin.itn.blog.mapper.domain.BlogPostHistoryVO;
import java.util.List;
import java.util.Map;
public interface BlogPosingService {
BlogAccountWithSourcesDTO getAccountWithSources(Long blogId);
/**
* 워크플로우 히스토리 조회 (페이징 지원)
*/
List<BlogPostHistoryVO> getWorkflowHistory(String blogId, String urlId, String status, int limit, int offset);
/**
* 워크플로우 통계 조회
*/
Map<String, Object> getWorkflowStatistics(Long blogId);
/**
* 실시간 진행상황 조회
*/
BlogPostHistoryVO getPublishProgress(Long postId);
/**
* 실패 원인별 분석
*/
List<Map<String, Object>> getFailureAnalysis(Long blogId, int days);
}

View File

@ -180,7 +180,7 @@ public class UserServiceImpl implements UserService {
return RestResponse.builder()
.status(HttpStatus.BAD_REQUEST) // 실패
.data(userVO.getUserName())
.msg("실패하였습니다.")
.msg("그룹웨어에 해당 이름으로 여러개 아이디가 등록되었습니다.\n확인해주세요")
.build();
}
userVO.setGwId(gwUserId);

View File

@ -1,2 +1,25 @@
agent.file.dir.path=X:\agent_file
spring.thymeleaf.cache=false
spring.thymeleaf.cache=false
# Tomcat 세션 영속화 비활성화 (개발 환경)
server.servlet.session.persistent=false
# 블로그 발행 설정
blog.generate.url=http://192.168.0.78:5000/blog/generate
blog.generate.timeout-ms=60000
# Python ?? ??
python.base-url=http://192.168.0.78:5000
python.path=/blog/generate
python.timeout-ms=20000
# Slack Notification Settings
slack.notification.enabled=true
slack.webhook.default=https://hooks.slack.com/services/YOUR_WEBHOOK_URL_HERE
slack.webhook.blog-schedule=https://hooks.slack.com/services/YOUR_BLOG_SCHEDULE_WEBHOOK_URL
slack.webhook.system-alert=https://hooks.slack.com/services/YOUR_SYSTEM_ALERT_WEBHOOK_URL
slack.channel.default=general
slack.channel.blog-schedule=blog-alerts
slack.channel.system-alert=system-alerts
slack.username=ITN-Admin
slack.icon.emoji=:robot_face:

View File

@ -1,2 +1,19 @@
agent.file.dir.path=/home/docker/tomcat_8081_to_8089_2022_0712/kcc_adr_volume/agent_file
spring.thymeleaf.cache=true
spring.thymeleaf.cache=true
# Python ?? ??
python.base-url=http://192.168.0.78:5000
python.path=/blog/generate
python.timeout-ms=20000
# Slack Notification Settings
slack.notification.enabled=true
slack.webhook.default=https://hooks.slack.com/services/YOUR_PRODUCTION_WEBHOOK_URL_HERE
slack.webhook.blog-schedule=https://hooks.slack.com/services/YOUR_PROD_BLOG_SCHEDULE_WEBHOOK_URL
slack.webhook.system-alert=https://hooks.slack.com/services/YOUR_PROD_SYSTEM_ALERT_WEBHOOK_URL
slack.channel.default=general
slack.channel.blog-schedule=blog-alerts
slack.channel.system-alert=system-alerts
slack.username=ITN-Admin-PROD
slack.icon.emoji=:warning:

File diff suppressed because one or more lines are too long

View File

@ -49,6 +49,7 @@
USER_ID
FROM INTRAWARE.USR_GLOBAL
WHERE NAME = #{userName}
AND STATUS = 1
</select>

View File

@ -30,23 +30,18 @@
<typeAlias type="com.itn.admin.itn.bizTrip.mapper.domain.BizTripApprovalVO" alias="bizTripApprovalVO"/>
<typeAlias type="com.itn.admin.gw.holiday.mapper.domain.HolidayVO" alias="holidayVO"/>
<!-- 블로그 예약 발행 관련 VO -->
<typeAlias type="com.itn.admin.itn.blog.mapper.domain.BlogScheduleVO" alias="blogScheduleVO"/>
<typeAlias type="com.itn.admin.itn.blog.mapper.domain.BlogScheduleExecutionVO" alias="blogScheduleExecutionVO"/>
<typeAlias type="com.itn.admin.itn.blog.mapper.domain.ScheduleCreateRequestDTO" alias="scheduleCreateRequestDTO"/>
<typeAlias type="com.itn.admin.itn.blog.mapper.domain.ScheduleUpdateRequestDTO" alias="scheduleUpdateRequestDTO"/>
<typeAlias type="com.itn.admin.itn.blog.mapper.domain.ScheduleSearchDTO" alias="scheduleSearchDTO"/>
<typeAlias type="com.itn.admin.itn.blog.mapper.domain.ScheduleStatisticsDTO" alias="scheduleStatisticsDTO"/>
<!-- 블로그 쿠키 매핑 관련 VO -->
<typeAlias type="com.itn.admin.itn.blog.mapper.domain.BlogCookieMappingVO" alias="blogCookieMappingVO"/>
</typeAliases>
<!-- <environments default="development">-->
<!-- <environment id="development">-->
<!-- <transactionManager type="JDBC">-->
<!-- <property name="..." value="..."/>-->
<!-- </transactionManager>-->
<!-- <dataSource type="POOLED">-->
<!-- <property name="driver" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>-->
<!-- <property name="url" value="jdbc:log4jdbc:mysql://192.168.0.30:3306/mjonUr_agent?serverTimezone=Asia/Seoul"/>-->
<!-- <property name="username" value="mjonAgentUr"/>-->
<!-- <property name="password" value="mjonAgentUr!@#$"/>-->
<!-- </dataSource>-->
<!-- </environment>-->
<!-- </environments>-->
<!-- <mappers>-->
<!-- <mapper resource="mapper/agent/AgentCThreeMapper.xml"/>-->
<!-- </mappers>-->
</configuration>

View File

@ -98,18 +98,6 @@
<!-- ./wrapper -->
<!-- DataTables & Plugins -->
<script th:src="@{/plugins/datatables/jquery.dataTables.min.js}"></script>
<script th:src="@{/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/dataTables.responsive.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/responsive.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/dataTables.buttons.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/jszip/jszip.min.js}"></script>
<script th:src="@{/plugins/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/plugins/pdfmake/vfs_fonts.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.html5.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.print.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.colVis.min.js}"></script>
<script>
$(function () {

View File

@ -170,18 +170,6 @@
<!-- ./wrapper -->
<!-- DataTables & Plugins -->
<script th:src="@{/plugins/datatables/jquery.dataTables.min.js}"></script>
<script th:src="@{/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/dataTables.responsive.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/responsive.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/dataTables.buttons.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/jszip/jszip.min.js}"></script>
<script th:src="@{/plugins/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/plugins/pdfmake/vfs_fonts.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.html5.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.print.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.colVis.min.js}"></script>
<script>
$(function () {

View File

@ -42,7 +42,12 @@
<link rel="stylesheet" th:href="@{/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css}">
<link rel="stylesheet" th:href="@{/plugins/datatables-responsive/css/responsive.bootstrap4.min.css}">
<link rel="stylesheet" th:href="@{/plugins/datatables-buttons/css/buttons.bootstrap4.min.css}">
<!-- SweetAlert2 CSS -->
<link rel="stylesheet" th:href="@{https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css}" >
@ -91,6 +96,20 @@
<!-- SweetAlert2 JS -->
<script th:src="@{https://cdn.jsdelivr.net/npm/sweetalert2@11}"></script>
<script th:src="@{/plugins/datatables/jquery.dataTables.min.js}"></script>
<script th:src="@{/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/dataTables.responsive.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/responsive.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/dataTables.buttons.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/jszip/jszip.min.js}"></script>
<script th:src="@{/plugins/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/plugins/pdfmake/vfs_fonts.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.html5.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.print.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.colVis.min.js}"></script>
<script>
/* 메뉴 하이라이팅 */

View File

@ -153,6 +153,41 @@
<p>단어사전</p>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" >
<i class="nav-icon fas fa-blog"></i>
<p>
블로그 자동 포스팅
<i class="right fas fa-angle-left"></i>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a th:href="@{/blog/accounts/list}" class="nav-link">
<i class="fas fa-users-cog nav-icon"></i>
<p>Blog 계정 관리</p>
</a>
</li>
<li class="nav-item">
<a th:href="@{/blog/sources/list}" class="nav-link">
<i class="fas fa-file-alt nav-icon"></i>
<p>포스팅 소스 관리</p>
</a>
</li>
<li class="nav-item">
<a th:href="@{/blog/posing/list}" class="nav-link">
<i class="fas fa-paper-plane nav-icon"></i>
<p>블로그 포스팅 실행</p>
</a>
</li>
<li class="nav-item">
<a th:href="@{/blog/history/list}" class="nav-link">
<i class="fas fa-history nav-icon"></i>
<p>포스팅 이력 조회</p>
</a>
</li>
</ul>
</li>
<!-- <li class="nav-item">
<a href="#" class="nav-link" >
<i class="nav-icon fas fa-sms"></i>

View File

@ -154,18 +154,6 @@
<!-- ./wrapper -->
<!-- DataTables & Plugins -->
<script th:src="@{/plugins/datatables/jquery.dataTables.min.js}"></script>
<script th:src="@{/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/dataTables.responsive.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/responsive.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/dataTables.buttons.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/jszip/jszip.min.js}"></script>
<script th:src="@{/plugins/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/plugins/pdfmake/vfs_fonts.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.html5.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.print.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.colVis.min.js}"></script>
<script>
const commonExportOptions = {

View File

@ -44,13 +44,13 @@
<table id="blogAccountTable" class="table table-bordered table-hover">
<thead>
<tr>
<th>블로그 ID</th>
<th>플랫폼</th>
<th></th>
<!-- <th>블로그 ID</th>-->
<th>블로그 이름</th>
<th>블로그 URL</th>
<th>포스팅 통계</th>
<th>등록일</th>
<th>수정일</th>
<th>액션</th>
</tr>
</thead>
<tbody>
@ -85,30 +85,53 @@
"autoWidth": false,
"responsive": true,
"ajax": {
"url": "/api/blog/accounts/active", // 활성 블로그 계정 목록 API
"dataSrc": ""
"url": "/api/blog/posing/list",
"dataSrc": "data"
},
"columns": [
{"data": "blog_id"},
{"data": "platform"},
{"data": "blog_name"},
{"data": "blog_url"},
{
"data": "reg_date",
"render": function (data, type, row) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
},
{
"data": "mod_date",
"render": function (data, type, row) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
},
{
"data": null,
"render": function (data, type, row) {
return '<button class="btn btn-info btn-sm manage-button" data-id="' + row.blog_id + '">관리</button>';
return '<button class="btn btn-info btn-sm manage-button" data-id="' + row.blogId + '">선택</button>';
}
},
// {"data": "blogId"},
{"data": "blogName"},
{"data": "blogUrl"},
{
"data": null,
"render": function (data, type, row) {
const postCount = row.totalPostCount || 0;
const lastPublished = row.lastPublishedAt ?
moment(row.lastPublishedAt).format('YYYY-MM-DD HH:mm') : '-';
// 발행 횟수에 따른 배지 색상 결정
let countBadgeClass = 'badge-secondary';
if (postCount > 50) countBadgeClass = 'badge-success';
else if (postCount > 20) countBadgeClass = 'badge-info';
else if (postCount > 0) countBadgeClass = 'badge-primary';
return `
<div class="text-center">
<span class="badge ${countBadgeClass} mb-1">${postCount}개 포스팅</span>
<br>
<small class="text-muted">
<i class="far fa-clock"></i> ${lastPublished}
</small>
</div>
`;
}
},
{
"data": "frstRegistPnttm",
"render": function (data, type, row) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
},
{
"data": "lastUpdtPnttm",
"render": function (data, type, row) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
}
]
@ -117,11 +140,11 @@
// 행 클릭 이벤트 (또는 관리 버튼 클릭 이벤트)
$('#blogAccountTable tbody').on('click', '.manage-button', function () {
const blogId = $(this).data('id');
location.href = '/blog/posting/manage/' + blogId; // 두 번째 페이지로 이동
location.href = '/blog/posing/manage/' + blogId; // 두 번째 페이지로 이동
});
});
</script>
</th:block>
</body>
</html>
</html>g

View File

@ -148,18 +148,6 @@
<!-- ./wrapper -->
<!-- DataTables & Plugins -->
<script th:src="@{/plugins/datatables/jquery.dataTables.min.js}"></script>
<script th:src="@{/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/dataTables.responsive.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/responsive.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/dataTables.buttons.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/jszip/jszip.min.js}"></script>
<script th:src="@{/plugins/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/plugins/pdfmake/vfs_fonts.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.html5.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.print.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.colVis.min.js}"></script>
<script>
$(function () {

View File

@ -345,18 +345,6 @@
<!-- ./wrapper -->
<!-- DataTables & Plugins -->
<script th:src="@{/plugins/datatables/jquery.dataTables.min.js}"></script>
<script th:src="@{/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/dataTables.responsive.min.js}"></script>
<script th:src="@{/plugins/datatables-responsive/js/responsive.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/dataTables.buttons.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.bootstrap4.min.js}"></script>
<script th:src="@{/plugins/jszip/jszip.min.js}"></script>
<script th:src="@{/plugins/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/plugins/pdfmake/vfs_fonts.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.html5.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.print.min.js}"></script>
<script th:src="@{/plugins/datatables-buttons/js/buttons.colVis.min.js}"></script>
<script>
$(function () {
const commonExportOptions = {