diff --git a/backend/framework/pom.xml b/backend/framework/pom.xml index 8d1b25f..aaedccc 100644 --- a/backend/framework/pom.xml +++ b/backend/framework/pom.xml @@ -52,6 +52,11 @@ bcpkix-jdk15on 1.68 + + org.dhatim + fastexcel + 0.18.4 + diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java index 7e6bd3d..0f9c9eb 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java @@ -104,7 +104,8 @@ public void onConnect(Connect connect) { public void createDataView(String schemaName, String tableName) { var viewTitle = this.getTitle() + "child"; - this.getPacketIO().sendPacket("new_data_view", Map.of("title", viewTitle)); + var data = Map.of("title", viewTitle, "schema", this.schema, "table", this.table); + this.getPacketIO().sendPacket("new_data_view", data); var dataView = new DataView(viewTitle, this.getPacketIO(), this.getConsoleLogger()); var session = SessionManager.getCurrentSession(); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java index f2d5e34..be4eb21 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java @@ -4,26 +4,18 @@ import lombok.EqualsAndHashCode; import org.jumpserver.chen.framework.console.action.DataViewAction; import org.jumpserver.chen.framework.console.component.Logger; +import org.jumpserver.chen.framework.console.dataview.export.DataExport; import org.jumpserver.chen.framework.console.entity.response.SQLResult; import org.jumpserver.chen.framework.console.state.DataViewState; import org.jumpserver.chen.framework.console.state.StateManager; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; import org.jumpserver.chen.framework.datasource.sql.SQLQueryParams; import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.session.SessionManager; -import org.jumpserver.chen.framework.utils.CodeUtils; import org.jumpserver.chen.framework.ws.io.PacketIO; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.sql.Clob; +import java.io.File; import java.sql.SQLException; -import java.text.SimpleDateFormat; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -74,7 +66,10 @@ public void doAction(DataViewAction action) throws SQLException { this.changeLimit((int) action.getData()); } case DataViewAction.ACTION_EXPORT -> { - this.export((String) action.getData()); + var data = (Map) action.getData(); + var scope = data.get("scope"); + var format = data.get("format"); + this.export(scope, format); } } } @@ -134,84 +129,39 @@ private void fullDataViewData(DataViewData viewData, SQLQueryResult result) { } - private static void writeString(BufferedWriter writer, Object object) throws IOException { - var str = object.toString(); - - if (str.contains(",")) { - str = "\"" + str + "\""; - } - writer.write(str); - } - - private void writeCSVData(BufferedWriter writer, DataViewData viewData) throws IOException, SQLException { - - for (Field field : viewData.getFields()) { - writeString(writer, field.getName()); - writer.write(","); - } - for (Map row : viewData.getData()) { - for (Field field : viewData.getFields()) { - var obj = row.get(field.getName()); - if (obj == null) { - writer.write("NULL"); - writer.write(","); - } else if (obj instanceof Clob clob) { - writer.write(CodeUtils.escapeCsvValue(clob.getSubString(1, (int) clob.length()))); - writer.write(","); - } else if (obj instanceof Date) { - SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - writeString(writer, fmt.format(obj)); - } else { - writeString(writer, row.get(field.getName())); - writer.write(","); - } - } - writer.newLine(); - } - - writer.newLine(); - } - - public void export(String scope) throws SQLException { + public void export(String scope, String format) throws SQLException { var session = SessionManager.getCurrentSession(); - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); - String timestamp = LocalDateTime.now().format(formatter); - var f = session.createFile(String.format("data_%s.csv", timestamp)); - CommandRecord command = new CommandRecord(String.format("Export data: %s", this.title)); try { if (!SessionManager.getCurrentSession().canDownload()) { - session.getController().sendFile(f.getName()); return; } - var writer = Files.newBufferedWriter(f.toPath()); - - if (scope.equals("current")) { - this.writeCSVData(writer, this.data); - command.setOutput(String.format("%d rows exported", this.data.getData().size())); - } - - if (scope.equals("all")) { - SQLQueryParams queryParams = new SQLQueryParams(); - queryParams.setLimit(-1); - var result = this.loadDataInterface.loadData(queryParams); - var viewData = new DataViewData(); - this.fullDataViewData(viewData, result); - command.setOutput(String.format("%d rows exported", result.getData().size())); + File f = null; + switch (scope) { + case "current": + f = DataExport.export(format, this.data); + command.setOutput(String.format("%d rows exported", this.data.getData().size())); + break; + case "all": + SQLQueryParams queryParams = new SQLQueryParams(); + queryParams.setLimit(-1); + var result = this.loadDataInterface.loadData(queryParams); + var viewData = new DataViewData(); + this.fullDataViewData(viewData, result); + f = DataExport.export(format, viewData); + command.setOutput(String.format("%d rows exported", result.getData().size())); + break; } - writer.flush(); - writer.close(); this.consoleLogger.success(command.getOutput()); session.recordCommand(command); + session.getController().sendFile(f.getName()); - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException(e); } - session.getController().sendFile(f.getName()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java new file mode 100644 index 0000000..a1255c7 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java @@ -0,0 +1,138 @@ +package org.jumpserver.chen.framework.console.dataview.export; + +import org.dhatim.fastexcel.Workbook; +import org.dhatim.fastexcel.Worksheet; +import org.jumpserver.chen.framework.console.dataview.DataViewData; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.utils.CodeUtils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Clob; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +import java.util.Map; + + +public class DataExport { + public static File export(String format, DataViewData data) throws Exception { + var session = SessionManager.getCurrentSession(); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); + String timestamp = LocalDateTime.now().format(formatter); + + String filename = String.format("data_%s", timestamp); + + File f; + + switch (format) { + case "excel": + f = session.createFile(String.format("%s.xlsx", filename)); + new DataExportExcel().exportData(f.toPath().toString(), data); + break; + case "csv": + f = session.createFile(String.format("%s.csv", filename)); + new DataExportCSV().exportData(f.toPath().toString(), data); + break; + default: + throw new Exception("unsupported format: " + format); + } + return f; + } +} + + +class DataExportExcel implements DataExportInterface { + @Override + public void exportData(String path, DataViewData data) throws Exception { + try (FileOutputStream fos = new FileOutputStream(path); + Workbook workbook = new Workbook(fos, "JumpServer", "4.0")) { + + Worksheet sheet = workbook.newWorksheet("Data"); + List fields = data.getFields(); + List> rows = data.getData(); + + for (int col = 0; col < fields.size(); col++) { + sheet.value(0, col, fields.get(col).getName()); + } + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { + Map row = rows.get(rowIndex); + + for (int col = 0; col < fields.size(); col++) { + Field field = fields.get(col); + Object obj = row.get(field.getName()); + + if (obj == null) { + sheet.value(rowIndex + 1, col, "NULL"); + } else if (obj instanceof Clob clob) { + try { + sheet.value(rowIndex + 1, col, clob.getSubString(1, (int) clob.length())); + } catch (Exception e) { + sheet.value(rowIndex + 1, col, "ERROR_CLOB"); + } + } else if (obj instanceof Date) { + sheet.value(rowIndex + 1, col, dateFormat.format(obj)); + } else { + sheet.value(rowIndex + 1, col, obj.toString()); + } + } + } + } + } +} + +class DataExportCSV implements DataExportInterface { + @Override + public void exportData(String path, DataViewData data) throws Exception { + var writer = Files.newBufferedWriter(Path.of(path)); + + for (Field field : data.getFields()) { + writeString(writer, field.getName()); + writer.write(","); + } + writer.newLine(); + for (Map row : data.getData()) { + for (Field field : data.getFields()) { + var obj = row.get(field.getName()); + if (obj == null) { + writer.write("NULL"); + writer.write(","); + } else if (obj instanceof Clob clob) { + writer.write(CodeUtils.escapeCsvValue(clob.getSubString(1, (int) clob.length()))); + writer.write(","); + } else if (obj instanceof Date) { + SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + writeString(writer, fmt.format(obj)); + } else { + writeString(writer, row.get(field.getName())); + writer.write(","); + } + } + writer.newLine(); + } + + writer.newLine(); + writer.flush(); + writer.close(); + } + + private static void writeString(BufferedWriter writer, Object object) throws IOException { + var str = object.toString(); + + if (str.contains(",")) { + str = "\"" + str + "\""; + } + writer.write(str); + } +} \ No newline at end of file diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExportInterface.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExportInterface.java new file mode 100644 index 0000000..2bfb075 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExportInterface.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console.dataview.export; + +import org.jumpserver.chen.framework.console.dataview.DataViewData; + +public interface DataExportInterface { + void exportData(String path, DataViewData data) throws Exception; +} \ No newline at end of file diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/controller/HealthCheckController.java b/backend/web/src/main/java/org/jumpserver/chen/web/controller/HealthCheckController.java new file mode 100644 index 0000000..3da55a1 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/controller/HealthCheckController.java @@ -0,0 +1,15 @@ +package org.jumpserver.chen.web.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/healthy") +public class HealthCheckController { + + @GetMapping("") + public String healthCheck() { + return "ok"; + } +} diff --git a/frontend/src/components/Main/Explore/DataView/DataView.vue b/frontend/src/components/Main/Explore/DataView/DataView.vue index 3383327..d147ece 100644 --- a/frontend/src/components/Main/Explore/DataView/DataView.vue +++ b/frontend/src/components/Main/Explore/DataView/DataView.vue @@ -1,7 +1,8 @@ diff --git a/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue b/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue index c4029e4..0371bc7 100644 --- a/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue +++ b/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue @@ -6,8 +6,16 @@ width="40%" > - {{ $tc('ExportCurrent') }} - {{ $tc('ExportAll') }} + + + {{ $tc('ExportCurrent') }} + {{ $tc('ExportAll') }} + + + + CSV + Excel + @@ -35,7 +43,8 @@ export default { data() { return { form: { - scope: 'current' + scope: 'current', + format: 'csv' } } }, @@ -53,7 +62,7 @@ export default { }, methods: { onSubmit() { - this.$emit('submit', this.form.scope) + this.$emit('submit', { scope: this.form.scope, format: this.form.format }) } } } diff --git a/frontend/src/components/Main/Explore/DataView/RightMenu.vue b/frontend/src/components/Main/Explore/DataView/RightMenu.vue new file mode 100644 index 0000000..8c6fb82 --- /dev/null +++ b/frontend/src/components/Main/Explore/DataView/RightMenu.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/frontend/src/components/Main/Explore/DataView/const.js b/frontend/src/components/Main/Explore/DataView/const.js new file mode 100644 index 0000000..d155671 --- /dev/null +++ b/frontend/src/components/Main/Explore/DataView/const.js @@ -0,0 +1,12 @@ +export const GeneralUpdateSQL = 'UPDATE {table_name} SET {updated_attrs} WHERE {conditional_attrs};' +export const GeneralInsertSQL = 'INSERT INTO {table_name} ({fields}) VALUES ({values});' + +export const SpecialCharacters = { + mysql: '`', + mariadb: '`', + postgresql: '"', + sqlserver: '', + oracle: '"', + dameng: '"', + db2: '' +}