diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d23f273 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +.git +.gitignore +node_modules +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +*.log +desktop/dist +desktop/node_modules +.idea +README.md +img + +**/.DS_Store +**/.git +**/.gitignore +**/.vscode +**/.idea + +**/node_modules +**/npm-debug.log +**/yarn-error.log +**/package-lock.json + +**/dist +**/build +**/coverage + +**/.env +**/.env.local +**/.env.*.local + + +**/README.md +**/CHANGELOG.md +**/logs +**/*.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..58b5ba9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# Build stage +FROM node:20-alpine as builder + +WORKDIR /app + +RUN apk add --no-cache git + +# Copy source code +COPY . . + +RUN corepack enable + + +# Install dependencies +RUN yarn install + +# Build application +RUN yarn web-build + +# Production stage +FROM node:20-alpine + +WORKDIR /app + + +RUN apk add --no-cache git + +# Create data directory +RUN mkdir -p /app/data + +# Copy necessary files +COPY --from=builder /app/package.json /app/yarn.lock /app/.yarnrc.yml ./ +COPY --from=builder /app/backEnd ./backEnd +COPY --from=builder /app/frontEnd/dist ./frontEnd/dist +COPY --from=builder /app/core ./core + +RUN corepack enable +# Install production dependencies +RUN yarn workspace back-end install + +# Set data directory permissions +RUN chown -R node:node /app/data + +# Switch to non-root user +USER node + +# Expose port +EXPOSE 9002 + +# Start application +CMD ["yarn", "web-start"] diff --git a/backEnd/package.json b/backEnd/package.json index 37d8d7c..88b0572 100644 --- a/backEnd/package.json +++ b/backEnd/package.json @@ -22,16 +22,12 @@ "@types/supertest": "^2.0.12", "@types/type-is": "^1.6.3", "jest": "^28.1.1", - "livemock-core": "workspace:*", "mockserver-client": "^5.13.2", "mockserver-node": "^5.13.2", "mockttp": "^3.1.0", "nodemon": "^2.0.16", "prettier": "2.8.7", - "supertest": "^6.3.3", - "ts-jest": "^28.0.5", - "ts-node": "^10.7.0", - "tsconfig-paths": "^4.2.0" + "ts-jest": "^28.0.5" }, "dependencies": { "@types/micromatch": "^4.0.7", @@ -39,15 +35,18 @@ "express": "4.18.1", "form-data": "^4.0.0", "http-proxy": "^1.18.1", - "livemock-core": "workspace:*", "lodash": "^4.17.21", - "lokijs": "git+https://github.com/alinGmail/LokiJS.git#94aa10183ab444d3f11d59a19314db39d3cb6ef3", + "lokijs": "git+https://github.com/alinGmail/LokiJS.git#265bbcaeafccac11ab556599e478380cd07ffca7", "micromatch": "^4.0.5", "mockjs": "^1.1.0", "nedb": "^1.8.0", "node-mocks-http": "^1.12.2", "requires-port": "^1.0.0", "socket.io": "^4.6.2", + "ts-node": "^10.7.0", + "tsconfig-paths": "^4.2.0", + "livemock-core": "workspace:*", + "supertest": "^6.3.3", "type-is": "^1.6.18" } -} +} \ No newline at end of file diff --git a/backEnd/src/config/config.ts b/backEnd/src/config/config.ts new file mode 100644 index 0000000..c8870d6 --- /dev/null +++ b/backEnd/src/config/config.ts @@ -0,0 +1,50 @@ +import path from 'path'; +import fs from 'fs'; + +interface Config { + database: { + path: string; + } +} + +// Default configuration +const defaultConfig: Config = { + database: { + path: 'db' + } +}; + +// Ensure directory exists +function ensureDirectoryExists(dirPath: string) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +// Check if running in test environment +function isTestEnvironment(): boolean { + return process.env.NODE_ENV === 'test' || + process.env.JEST_WORKER_ID !== undefined || + process.argv.some(arg => arg.includes('jest')); +} + +// Load configuration from environment variables +export function loadConfig(): Config { + const config = { + database: { + path: process.env.LIVEMOCK_DB_PATH || defaultConfig.database.path + } + }; + + // Only create directory in non-test environment + if (!isTestEnvironment()) { + ensureDirectoryExists(config.database.path); + } + + return config; +} + +// Get current configuration +export function getConfig(): Config { + return loadConfig(); +} \ No newline at end of file diff --git a/backEnd/src/controller/logController.ts b/backEnd/src/controller/logController.ts index 6eef790..a215087 100644 --- a/backEnd/src/controller/logController.ts +++ b/backEnd/src/controller/logController.ts @@ -13,6 +13,10 @@ import { DeleteAllRequestLogsPathParam, DeleteAllRequestLogsReqBody, DeleteAllRequestLogsReqQuery, + GetLogDetailPathParam, + GetLogDetailReqBody, + GetLogDetailReqQuery, + GetLogDetailResponse, ListLogPathParam, ListLogReqBody, ListLogReqQuery, @@ -78,6 +82,7 @@ export async function getLogRouter(path: string): Promise { } ); + /** * list the log view */ @@ -104,6 +109,39 @@ export async function getLogRouter(path: string): Promise { } ); + + /** + * get log item + */ + router.get( + `/detail/:logId`, + async ( + req: Request< + GetLogDetailPathParam, + GetLogDetailResponse, + GetLogDetailReqBody, + GetLogDetailReqQuery + >, + res: Response + ) => { + addCross(res); + const logId = parseInt(req.params.logId); + if (!logId) { + throw new ServerError(400, "log id not exist!"); + } + const projectId = req.query.projectId; + const collection = await getLogCollection(projectId, path); + + const logItem = collection.findOne({ id: logId }); + if (logItem === null) { + throw new ServerError(500, "log item not found!"); + } + res.json({ + logItem, + }); + } + ); + /** * get the log view logs */ diff --git a/backEnd/src/index.ts b/backEnd/src/index.ts index 0074aa4..46b62c5 100644 --- a/backEnd/src/index.ts +++ b/backEnd/src/index.ts @@ -10,6 +10,7 @@ import { getSystemCollection } from "./db/dbManager"; import { sysEventEmitter } from "./common/eventEmitters"; import { SystemEvent } from "livemock-core/struct/events/systemEvent"; import { addWsEventListeners } from "./common/eventListener"; +import { getConfig } from "./config/config"; const { Server } = require("socket.io"); @@ -22,8 +23,11 @@ const io = new Server(http, { }); export const systemVersion = 801; +const config = getConfig(); +const dbPath = config.database.path; + sysEventEmitter.on(SystemEvent.START, async () => { - const systemCollection = await getSystemCollection("db"); + const systemCollection = await getSystemCollection(dbPath); const systemConfig = systemCollection.findOne({}); if (systemConfig) { } else { @@ -39,17 +43,17 @@ addWsEventListeners(); sysEventEmitter.listeners(SystemEvent.START).map((listener) => listener()) ); - server.use("/project", await getProjectRouter("db")); - server.use("/expectation", getExpectationRouter("db")); - server.use("/matcher", getMatcherRouter("db")); - server.use("/action", await getActionRouter("db")); - server.use("/logFilter", await getLogFilterRouter("db")); - server.use("/log", await getLogRouter("db")); + server.use("/project", await getProjectRouter(dbPath)); + server.use("/expectation", getExpectationRouter(dbPath)); + server.use("/matcher", getMatcherRouter(dbPath)); + server.use("/action", await getActionRouter(dbPath)); + server.use("/logFilter", await getLogFilterRouter(dbPath)); + server.use("/log", await getLogRouter(dbPath)); server.use("/dashboard", express.static("../frontEnd/dist")); server.all("/", (req, res) => { res.redirect("/dashboard"); }); - await addLogListener(io, "db"); + await addLogListener(io, dbPath); server.use(CustomErrorMiddleware); http.listen(9002, () => { diff --git a/core/struct/events/desktopEvents.ts b/core/struct/events/desktopEvents.ts index 4f8194f..97b0669 100644 --- a/core/struct/events/desktopEvents.ts +++ b/core/struct/events/desktopEvents.ts @@ -35,6 +35,7 @@ export enum ActionEvents { export enum LogViewEvents{ ListLogView="ListLogView", ListLogViewLogs="ListLogViewLogs", + GetLogDetail="GetLogDetail", DeleteAllRequestLogs="DeleteAllRequestLogs", OnLogAdd="OnLogViewLogAdd", OnLogUpdate="OnLogViewLogUpdate", @@ -54,4 +55,5 @@ export enum LogFilterEvents{ export enum SystemEvents{ OpenAboutWindow = "OpenAboutWindow", + OpenNewWindow = "OpenNewWindow", } \ No newline at end of file diff --git a/core/struct/params/LogParams.ts b/core/struct/params/LogParams.ts index 62fa38f..10b2fcf 100644 --- a/core/struct/params/LogParams.ts +++ b/core/struct/params/LogParams.ts @@ -1,66 +1,69 @@ +import { LogM } from "../log"; + /** * list log */ -export interface ListLogPathParam{ +export interface ListLogPathParam {} + +export interface ListLogReqBody {} +export interface ListLogReqQuery { + maxLogId?: number; + projectId: string; } -export interface ListLogReqBody{ +/** + * get log detail + */ +export interface GetLogDetailPathParam { + logId: string; +} - +export interface GetLogDetailReqBody { } -export interface ListLogReqQuery{ - maxLogId?:number; - projectId:string; +export interface GetLogDetailReqQuery { + projectId: string; } +export interface GetLogDetailResponse { + logItem: LogM; +} /** * list log view */ -export interface ListLogViewPathParam{ - -} - -export interface ListLogViewReqBody{ +export interface ListLogViewPathParam {} -} +export interface ListLogViewReqBody {} -export interface ListLogViewReqQuery{ - projectId:string; +export interface ListLogViewReqQuery { + projectId: string; } - /** * list logs by logView */ -export interface ListLogViewLogsPathParam{ - logViewId:string; +export interface ListLogViewLogsPathParam { + logViewId: string; } -export interface ListLogViewLogsReqBody{ - -} +export interface ListLogViewLogsReqBody {} -export interface ListLogViewLogsReqQuery{ - projectId:string; - maxLogId:string|null; +export interface ListLogViewLogsReqQuery { + projectId: string; + maxLogId: string | null; } /** * delete all logs by logView */ -export interface DeleteAllRequestLogsPathParam{ - -} +export interface DeleteAllRequestLogsPathParam {} -export interface DeleteAllRequestLogsReqBody{ +export interface DeleteAllRequestLogsReqBody {} +export interface DeleteAllRequestLogsReqQuery { + projectId: string; } - -export interface DeleteAllRequestLogsReqQuery{ - projectId:string; -} \ No newline at end of file diff --git a/desktop/electron/handler/logViewHandler.ts b/desktop/electron/handler/logViewHandler.ts index bab88f8..509cfc9 100644 --- a/desktop/electron/handler/logViewHandler.ts +++ b/desktop/electron/handler/logViewHandler.ts @@ -1,10 +1,16 @@ import * as electron from "electron"; import ipcMain = electron.ipcMain; -import { LogEvents, LogViewEvents } from "livemock-core/struct/events/desktopEvents"; +import { + LogEvents, + LogViewEvents, +} from "livemock-core/struct/events/desktopEvents"; import { DeleteAllRequestLogsPathParam, DeleteAllRequestLogsReqBody, DeleteAllRequestLogsReqQuery, + GetLogDetailPathParam, + GetLogDetailReqBody, + GetLogDetailReqQuery, ListLogViewLogsPathParam, ListLogViewLogsReqBody, ListLogViewLogsReqQuery, @@ -48,7 +54,7 @@ export async function setLogViewHandler(path: string) { reqQuery: ListLogViewLogsReqQuery, reqBody: ListLogViewLogsReqBody ) => { - let { maxLogId, projectId } = reqQuery; + const { maxLogId, projectId } = reqQuery; const lovViewId = reqParam.logViewId; if (!projectId) { throw new ServerError(400, "project id not exist!"); @@ -73,6 +79,29 @@ export async function setLogViewHandler(path: string) { } ); + ipcMain.handle( + LogViewEvents.GetLogDetail, + async ( + event, + reqParam: GetLogDetailPathParam, + reqQuery: GetLogDetailReqQuery, + reqBody: GetLogDetailReqBody + ) => { + const logId = parseInt(reqParam.logId); + if (!logId) { + throw new ServerError(400, "log id not exist!"); + } + const projectId = reqQuery.projectId; + const collection = await getLogCollection(projectId, path); + + const logItem = collection.findOne({ id: logId }); + if (logItem === null) { + throw new ServerError(500, "log item not found!"); + } + return { logItem }; + } + ); + ipcMain.handle( LogViewEvents.DeleteAllRequestLogs, async ( @@ -100,24 +129,24 @@ export function logViewEventHandler(webContent: WebContents) { } logViewEventHandlerInit = true; logViewEventEmitter.on("insert", (arg: { log: LogM; logViewId: string }) => { - let { log, logViewId } = arg; + const { log, logViewId } = arg; webContent.send(LogViewEvents.OnLogAdd, { log, logViewId }); }); logViewEventEmitter.on("update", (arg: { log: LogM; logViewId: string }) => { - let { log, logViewId } = arg; + const { log, logViewId } = arg; webContent.send(LogViewEvents.OnLogUpdate, { log, logViewId }); }); logViewEventEmitter.on("delete", (arg: { log: LogM; logViewId: string }) => { - let { log, logViewId } = arg; + const { log, logViewId } = arg; webContent.send(LogViewEvents.OnLogDelete, { log, logViewId }); }); logEventEmitter.on( "update", (arg: { projectId: string; log: LogM; oldLog: LogM }) => { - let { oldLog, log, projectId } = arg; + const { oldLog, log, projectId } = arg; webContent.send(LogEvents.OnLogUpdate, { log, projectId }); } ); diff --git a/desktop/electron/main.ts b/desktop/electron/main.ts index 0e19d90..2136545 100644 --- a/desktop/electron/main.ts +++ b/desktop/electron/main.ts @@ -16,10 +16,10 @@ import { systemVersion } from "./config"; import * as electron from "electron"; import ipcMain = electron.ipcMain; import { SystemEvents } from "livemock-core/struct/events/desktopEvents"; -import log from 'electron-log/main'; +import log from "electron-log/main"; import { sysEventEmitter } from "./common/eventEmitters"; import { SystemEvent } from "livemock-core/struct/events/systemEvent"; -import {addWsEventListeners} from "./common/eventListener"; +import { addWsEventListeners } from "./common/eventListener"; log.initialize(); log.errorHandler.startCatching(); @@ -44,8 +44,7 @@ const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; const env = process.env["PROJECT_ENV"]; - -sysEventEmitter.on(SystemEvent.START,async () => { +sysEventEmitter.on(SystemEvent.START, async () => { const systemCollection = await getSystemCollection(app.getPath("userData")); const systemConfig = systemCollection.findOne({}); if (systemConfig) { @@ -63,7 +62,7 @@ async function createWindow() { }, }); await Promise.all( - sysEventEmitter.listeners(SystemEvent.START).map((listener) => listener()) + sysEventEmitter.listeners(SystemEvent.START).map((listener) => listener()) ); await setProjectHandler(app.getPath("userData")); @@ -89,10 +88,10 @@ async function createWindow() { if (env === "dev") { win.loadURL("http://localhost:5173"); - win.webContents.on('did-fail-load', () => { + win.webContents.on("did-fail-load", () => { setTimeout(() => { win?.loadURL("http://localhost:5173"); - },3000); + }, 3000); }); win.webContents.openDevTools(); } else { @@ -101,6 +100,31 @@ async function createWindow() { } } +function openNewWindow(hash: string, width: number, height: number) { + if (!win) { + return; + } + const newWin = new BrowserWindow({ + modal: false, + show: true, + width: width, + height: height, + icon: path.join(process.env.PUBLIC, "logo.png"), + webPreferences: { + preload: path.join(__dirname, "preload.js"), + nodeIntegration: true, + }, + }); + if (env === "dev") { + newWin.loadURL(`http://localhost:5173/#${hash}`); + newWin.webContents.openDevTools(); + } else { + newWin.loadFile(path.join(process.env.DIST, "index.html"), { + hash: hash, + }); + } +} + function createAboutWindow() { if (!win) { return; @@ -121,10 +145,14 @@ function createAboutWindow() { aboutWin.loadFile(path.join(process.env.DIST, `about.html`)); } aboutWin.webContents.on("did-finish-load", () => { - aboutWin.webContents.executeJavaScript(` + aboutWin.webContents + .executeJavaScript( + ` const ele = document.querySelector("#version"); ele.innerHTML = '${app.getVersion()}'; - `).catch(console.error); + ` + ) + .catch(console.error); }); } @@ -132,6 +160,13 @@ ipcMain.handle(SystemEvents.OpenAboutWindow, () => { createAboutWindow(); }); +ipcMain.handle( + SystemEvents.OpenNewWindow, + (event,hash: string, width: number, height: number) => { + openNewWindow(hash, width, height); + } +); + app.on("window-all-closed", () => { win = null; app.quit(); diff --git a/desktop/electron/preload.ts b/desktop/electron/preload.ts index a2f7ef2..b8ef25c 100644 --- a/desktop/electron/preload.ts +++ b/desktop/electron/preload.ts @@ -18,10 +18,12 @@ import * as electron from "electron"; import { ActionEvents, ExpectationEvents, + LogEvents, LogFilterEvents, LogViewEvents, MatcherEvents, ProjectEvents, + SystemEvents, } from "livemock-core/struct/events/desktopEvents"; import { CreateExpectationPathParam, @@ -67,6 +69,9 @@ import { DeleteAllRequestLogsPathParam, DeleteAllRequestLogsReqBody, DeleteAllRequestLogsReqQuery, + GetLogDetailPathParam, + GetLogDetailReqBody, + GetLogDetailReqQuery, ListLogViewLogsPathParam, ListLogViewLogsReqBody, ListLogViewLogsReqQuery, @@ -143,7 +148,7 @@ export const api = { ); }, deleteProject: ({ projectId }: { projectId: string }) => { - return ipcRenderer.invoke(ProjectEvents.DeleteProject,projectId); + return ipcRenderer.invoke(ProjectEvents.DeleteProject, projectId); }, startProject: ({ projectId }: { projectId: string }) => { return ipcRenderer.invoke(ProjectEvents.StartProject, projectId); @@ -339,6 +344,18 @@ export const api = { reqBody ); }, + getLogDetail: ( + reqParam: GetLogDetailPathParam, + reqQuery: GetLogDetailReqQuery, + reqBody: GetLogDetailReqBody + ) => { + return ipcRenderer.invoke( + LogViewEvents.GetLogDetail, + reqParam, + reqQuery, + reqBody + ); + }, }, logFilter: { createLogFilter: ( @@ -390,6 +407,16 @@ export const api = { ); }, }, + system: { + openNewWindow: (hash: string, width: number, height: number) => { + return ipcRenderer.invoke( + SystemEvents.OpenNewWindow, + hash, + width, + height + ); + }, + }, }; electron.contextBridge.exposeInMainWorld("api", api); diff --git a/desktop/package.json b/desktop/package.json index 3cbe1ed..6f0bb67 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -27,7 +27,7 @@ "http-proxy": "^1.18.1", "livemock-core": "workspace:*", "lodash": "^4.17.21", - "lokijs": "git+https://github.com/alinGmail/LokiJS.git#94aa10183ab444d3f11d59a19314db39d3cb6ef3", + "lokijs": "git+https://github.com/alinGmail/LokiJS.git#265bbcaeafccac11ab556599e478380cd07ffca7", "micromatch": "^4.0.5", "mockjs": "^1.1.0", "modern-css-reset": "^1.4.0", diff --git a/desktop/src/App.css b/desktop/src/App.css index 61f471a..6754d1c 100644 --- a/desktop/src/App.css +++ b/desktop/src/App.css @@ -130,3 +130,17 @@ html { ) repeat-x; } + +.blank10, +.blank20 { + height: 10px; + width: 100%; + clear: both; + display: block; + overflow: hidden; + line-height: 10px; +} +.blank20{ + height: 20px; + line-height: 20px; +} diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx index 6b234c7..8938989 100644 --- a/desktop/src/App.tsx +++ b/desktop/src/App.tsx @@ -9,11 +9,12 @@ import { setProjectList } from "./slice/projectSlice"; import { useAppSelector } from "./store"; import { Toaster } from "react-hot-toast"; import { Spin, ConfigProvider, theme, App as AntApp } from "antd"; -import { Route, Routes, Navigate } from "react-router-dom"; +import {Route, Routes, Navigate, HashRouter} from "react-router-dom"; import ExpectationPage from "./page/ExpectationPage"; import ConfigPage from "./page/ConfigPage"; import LogPage from "./page/LogPage"; import { useEffect } from "react"; +import RequestLogDetailPage from "./page/RequestLogDetail/RequestLogDetailPage"; function App() { const dispatch = useDispatch(); @@ -54,14 +55,21 @@ function App() { projectList.length === 0 ? ( ) : ( - - - } /> - }> - } /> - } /> - - + + + }> + } /> + }> + } /> + } /> + + } + /> + + + ) ) : ( diff --git a/desktop/src/component/Layout.tsx b/desktop/src/component/Layout.tsx index c50e1ab..71c3659 100644 --- a/desktop/src/component/Layout.tsx +++ b/desktop/src/component/Layout.tsx @@ -1,28 +1,24 @@ import mStyle from "./Layout.module.scss"; import LeftNav from "./LeftNav"; -import { HashRouter } from "react-router-dom"; +import { Outlet } from "react-router-dom"; import ProjectInfo from "./project/ProjectInfo"; import * as React from "react"; -const Layout:React.FC<{ - children:React.ReactNode -}> = (props) => { +const Layout: React.FC = () => { return ( - -
-
- +
+
+ +
+
+
+
-
-
- -
-
- {props.children} -
+
+
- +
); }; diff --git a/desktop/src/page/LogPage.tsx b/desktop/src/page/LogPage.tsx index eb4e2c4..9820c77 100644 --- a/desktop/src/page/LogPage.tsx +++ b/desktop/src/page/LogPage.tsx @@ -35,7 +35,10 @@ import { listLogViewReq, listLogViewLogs } from "../server/logServer"; import { binarySearch, toastPromise } from "../component/common"; import { Updater, useImmer } from "use-immer"; import { ColumnsType } from "antd/es/table/interface"; -import { LogEvents, LogViewEvents } from "livemock-core/struct/events/desktopEvents"; +import { + LogEvents, + LogViewEvents, +} from "livemock-core/struct/events/desktopEvents"; import IpcRendererEvent = Electron.IpcRendererEvent; import FilterRowComponent from "../component/log/FilterRowComponent"; import PresetFilterRowComponent from "../component/log/PresetFilterRowComponent"; @@ -48,6 +51,7 @@ import ChatMainComponent, { } from "../component/chat/ChatMainComponent"; import { DisconnectOutlined, LinkOutlined } from "@ant-design/icons"; import { red, green } from "@ant-design/colors"; +import { useNavigate } from "react-router-dom"; function onLogsInsert( insertLog: LogM, @@ -131,6 +135,7 @@ const LogPage: React.FC = () => { const projectState = useAppSelector((state) => state.project); const currentProject = projectState.projectList[projectState.curProjectIndex]; const [logs, setLogs] = useImmer>([]); + const navigate = useNavigate(); const expectationState = useAppSelector((state) => state.expectation); @@ -215,7 +220,7 @@ const LogPage: React.FC = () => { ) .filter((item, index) => defaultColumnVisible[index]) .concat(customColumns) - .concat(getConfigColumn(dispatch)); + .concat(getConfigColumn(dispatch, navigate, currentProject.id)); updateLogColumn(newLogColumn); }, [ tableColumns, diff --git a/desktop/src/page/LogPageColumn.tsx b/desktop/src/page/LogPageColumn.tsx index b6f7650..62b282d 100644 --- a/desktop/src/page/LogPageColumn.tsx +++ b/desktop/src/page/LogPageColumn.tsx @@ -5,6 +5,7 @@ import { DeleteOutlined, FilterOutlined, MessageOutlined, + FileSearchOutlined, } from "@ant-design/icons"; import { createSimpleFilter, FilterType, LogM } from "livemock-core/struct/log"; import { Dispatch, useState } from "react"; @@ -29,7 +30,7 @@ import mStyle from "./LogPageColumn.module.scss"; import { ReactComponent as Equalizer } from "../svg/equalizer.svg"; import { ReactComponent as Eye } from "../svg/eye.svg"; import { ReactComponent as EyeBlocked } from "../svg/eye-blocked.svg"; -import _ from "lodash"; +import _, { after } from "lodash"; import ReactJson from "react-json-view"; import { v4 as uuId } from "uuid"; import TextColumn from "../component/table/TextColumn"; @@ -37,14 +38,36 @@ import { addLogFilterReq } from "../server/logFilterServer"; import { toastPromise } from "../component/common"; import ExpectationBriefComponent from "../component/log/ExpectationBriefComponent"; import { ExpectationM } from "livemock-core/build/struct/expectation"; +import { NavigateFunction } from "react-router-dom"; -export function getConfigColumn(dispatch: Dispatch) { +export function getConfigColumn( + dispatch: Dispatch, + navigate: NavigateFunction, + projectId: string +) { return [ { dataIndex: "config", key: "config", width: "100px", - render: () =>
, + render: (text: string, record: LogM) => { + return ( +
+
+ ); + }, title: () => { return (
diff --git a/desktop/src/page/RequestLogDetail/RequestBodyCard.tsx b/desktop/src/page/RequestLogDetail/RequestBodyCard.tsx new file mode 100644 index 0000000..0e0d116 --- /dev/null +++ b/desktop/src/page/RequestLogDetail/RequestBodyCard.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import mStyle from "./RequestLogDetailPage.module.scss"; + +const RequestBodyCard: React.FunctionComponent<{ + body: any; +}> = ({ body }) => { + return ( +
+
Request Body
+
+
{JSON.stringify(body, null, 2)}
+
+
+ ); +}; + +export default RequestBodyCard; diff --git a/desktop/src/page/RequestLogDetail/RequestHeadersCard.tsx b/desktop/src/page/RequestLogDetail/RequestHeadersCard.tsx new file mode 100644 index 0000000..711e771 --- /dev/null +++ b/desktop/src/page/RequestLogDetail/RequestHeadersCard.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import mStyle from "./RequestLogDetailPage.module.scss"; + +const RequestHeadersCard: React.FunctionComponent<{ + headers: { + [key: string]: string | null | undefined; + }; +}> = ({ headers }) => { + return ( +
+
Request Headers
+
+ {Object.keys(headers).map((key: string) => { + return ( +
+
{key}
+
{headers[key] ?? ''}
+
+ ); + })} +
+
+ ); +}; + +export default RequestHeadersCard; diff --git a/desktop/src/page/RequestLogDetail/RequestLogDetailPage.module.scss b/desktop/src/page/RequestLogDetail/RequestLogDetailPage.module.scss new file mode 100644 index 0000000..fa5cc0c --- /dev/null +++ b/desktop/src/page/RequestLogDetail/RequestLogDetailPage.module.scss @@ -0,0 +1,114 @@ +.req_log_detail { + .req_tile { + color: #fff; + background-color: rgb(37, 99, 235); + padding: 2rem; + display: flex; + align-items: center; + justify-content: space-between; + } + .til_left { + display: flex; + align-items: center; + } + .til_right { + display: flex; + align-items: center; + } + .req_method { + font-size: 2.2rem; + font-weight: bold; + line-height: 3.5rem; + margin-right: 2rem; + } + .req_path { + font-size: 2.2rem; + line-height: 3.5rem; + } + .req_time { + font-size: 1.8rem; + line-height: 2.5rem; + margin-right: 2rem; + } + .req_status { + font-size: 1.8rem; + line-height: 2.5rem; + padding: .5rem 2rem; + border-radius: 9.9rem; + } + + .req_status_processing{ + background-color: #ff7a45; + } + .req_status_error{ + background-color: #ff4d4f; + } + .req_status_success{ + // background-color: #52c41a; + background-color: rgb(16, 185, 129); + } + + .req_content { + display: grid; + padding: 2rem; + gap: 2rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + background:#fff; + + .request_col { + } + .response_col { + } + .card { + padding: 2rem; + background-color: rgb(249, 250, 251); + border-radius: 1rem; + color: #000; + } + .card_til { + font-size: 2.2rem; + line-height: 3.5rem; + display: flex; + align-items: center; + margin-bottom: 1.5rem; + font-weight: 600; + } + .card_content { + } + .property_row { + font-size: 1.6rem; + line-height: 2.2rem; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 1rem; + margin-bottom: 1rem; + word-break: break-all; + .pro_label { + color: rgb(75 85 99); + } + .pro_value { + } + } + } + + .json_pre { + font-size: 1.4rem; + line-height: 2.2rem; + padding: 2rem; + white-space: pre-wrap; + color: rgb(52 211 153); + background-color: rgb(17 24 39); + border-radius: 1rem; + } +} + +html{ + font-size: 8px; + @media (min-width: 768px) { + font-size: 10px; + } + @media (min-width: 1024px) { + font-size: 12px; + } +} + diff --git a/desktop/src/page/RequestLogDetail/RequestLogDetailPage.tsx b/desktop/src/page/RequestLogDetail/RequestLogDetailPage.tsx new file mode 100644 index 0000000..47869f3 --- /dev/null +++ b/desktop/src/page/RequestLogDetail/RequestLogDetailPage.tsx @@ -0,0 +1,102 @@ +import mStyle from "./RequestLogDetailPage.module.scss"; +import RequestHeadersCard from "./RequestHeadersCard"; +import RequestBodyCard from "./RequestBodyCard"; +import { useParams, useSearchParams } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; +import { getRequestLogDetail } from "../../server/logServer"; +import { ClockCircleOutlined } from "@ant-design/icons"; +import RequestQueryCard from "front-end/src/page/RequestLogDetail/RequestQueryCard"; + + +function isEmptyObject(obj: any): boolean { + if(!obj){ + return true; + } + return Object.keys(obj).length === 0; +} + + +const RequestLogDetailPage = () => { + const params = useParams<{ + logId: string; + }>(); + const [searchParam, setSearchParam] = useSearchParams(); + const projectId = searchParam.get("projectId"); + if (projectId === null) { + throw new Error("No projectId provided"); + } + const getLogDetailQuery = useQuery([params.logId], () => { + if (!params.logId) { + throw new Error("No log detail found."); + } + return getRequestLogDetail(params.logId, projectId); + }); + function getStatusClass(reqCode: number | undefined) { + if (!reqCode) { + return ""; + } + if (reqCode < 200) { + return mStyle.req_status_processing; + } else if (reqCode < 300) { + return mStyle.req_status_success; + } else if (reqCode < 400) { + return mStyle.req_status_processing; + } else { + return mStyle.req_status_error; + } + } + return ( +
+
+
+
+ {getLogDetailQuery.data?.logItem.req?.method} +
+
+ {getLogDetailQuery.data?.logItem.req?.path} +
+
+
+
+ 2025-01-10 12:00:00 +
+
+ {getLogDetailQuery.data?.logItem.res?.status} +
+
+
+ +
+
+ + {!isEmptyObject(getLogDetailQuery.data?.logItem.req?.query) && ( + <> +
+ + + )} +
+ +
+
+ +
+ +
+
+
+ ); +}; + +export default RequestLogDetailPage; diff --git a/desktop/src/page/RequestLogDetail/RequestQueryCard.tsx b/desktop/src/page/RequestLogDetail/RequestQueryCard.tsx new file mode 100644 index 0000000..fa7bed1 --- /dev/null +++ b/desktop/src/page/RequestLogDetail/RequestQueryCard.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import mStyle from "./RequestLogDetailPage.module.scss"; + +const RequestBodyCard: React.FunctionComponent<{ + query: any; +}> = ({ query }) => { + return ( +
+
Request Query
+
+
{JSON.stringify(query, null, 2)}
+
+
+ ); +}; + +export default RequestBodyCard; diff --git a/desktop/src/server/logServer.ts b/desktop/src/server/logServer.ts index c15c3c7..f474fd5 100644 --- a/desktop/src/server/logServer.ts +++ b/desktop/src/server/logServer.ts @@ -1,5 +1,6 @@ import { DeleteAllRequestLogsReqQuery, + GetLogDetailResponse, ListLogViewLogsReqQuery, ListLogViewReqQuery, } from "livemock-core/struct/params/LogParams"; @@ -27,3 +28,14 @@ export async function deleteAllRequestLogs( ): Promise { return window.api.logView.deleteAllRequestLogs({}, query, {}); } + +export async function getRequestLogDetail( + logId: string, + projectId: string +): Promise { + return window.api.logView.getLogDetail( + { logId }, + { projectId: projectId }, + {} + ); +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8fa730b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.8" + +services: + livemock: + build: + context: . + dockerfile: Dockerfile + ports: + - "9002:9002" + environment: + - NODE_ENV=production + - LIVEMOCK_DB_PATH=/app/data/db + restart: unless-stopped + volumes: + - livemock_data:/app/data + +volumes: + livemock_data: \ No newline at end of file diff --git a/frontEnd/package.json b/frontEnd/package.json index 24ae366..8935a6d 100644 --- a/frontEnd/package.json +++ b/frontEnd/package.json @@ -23,6 +23,7 @@ "antd": "^5.4.6", "lodash": "^4.17.21", "modern-css-reset": "^1.4.0", + "monaco-editor": "^0.47.0", "react": "^18.2.0", "react-contenteditable": "^3.3.7", "react-dom": "^18.2.0", diff --git a/frontEnd/src/App.css b/frontEnd/src/App.css index 93846f4..a70434a 100644 --- a/frontEnd/src/App.css +++ b/frontEnd/src/App.css @@ -141,3 +141,17 @@ html { ) repeat-x; } + +.blank10, +.blank20 { + height: 10px; + width: 100%; + clear: both; + display: block; + overflow: hidden; + line-height: 10px; +} +.blank20{ + height: 20px; + line-height: 20px; +} \ No newline at end of file diff --git a/frontEnd/src/App.tsx b/frontEnd/src/App.tsx index 57d760f..709242c 100644 --- a/frontEnd/src/App.tsx +++ b/frontEnd/src/App.tsx @@ -9,11 +9,12 @@ import { setProjectList } from "./slice/projectSlice"; import { useAppSelector } from "./store"; import { Toaster } from "react-hot-toast"; import { ConfigProvider, Spin, theme, App as AntApp } from "antd"; -import { Route, Routes, Navigate } from "react-router-dom"; +import { Route, Routes, Navigate, HashRouter } from "react-router-dom"; import ExpectationPage from "./page/ExpectationPage"; import LogPage from "./page/LogPage"; import ConfigPage from "./page/ConfigPage"; import { useEffect } from "react"; +import RequestLogDetailPage from "./page/RequestLogDetail/RequestLogDetailPage"; function App() { const dispatch = useDispatch(); @@ -30,11 +31,11 @@ function App() { useEffect(() => { if (systemConfigState.mode === "dark") { document.body.className = "dark_mode"; - document.documentElement.style.colorScheme = 'dark'; + document.documentElement.style.colorScheme = "dark"; document.getElementsByTagName("html")[0].style.background = "linear-gradient(to bottom, #262626 0, #262626 73px, #595959 73px) repeat-x"; } else { - document.documentElement.style.colorScheme = 'light'; + document.documentElement.style.colorScheme = "light"; document.getElementsByTagName("html")[0].style.background = "linear-gradient(to bottom, rgb(36, 41, 47) 0, rgb(36, 41, 47) 73px, white 73px) repeat-x"; document.body.className = ""; @@ -55,14 +56,23 @@ function App() { projectList.length === 0 ? ( ) : ( - + - } /> - } /> - } /> - } /> + }> + } /> + } /> + } /> + } + /> + + } + /> - + ) ) : ( diff --git a/frontEnd/src/component/Layout.tsx b/frontEnd/src/component/Layout.tsx index c50e1ab..df56e6f 100644 --- a/frontEnd/src/component/Layout.tsx +++ b/frontEnd/src/component/Layout.tsx @@ -1,28 +1,24 @@ import mStyle from "./Layout.module.scss"; import LeftNav from "./LeftNav"; -import { HashRouter } from "react-router-dom"; +import { HashRouter, Outlet } from "react-router-dom"; import ProjectInfo from "./project/ProjectInfo"; import * as React from "react"; -const Layout:React.FC<{ - children:React.ReactNode -}> = (props) => { +const Layout: React.FC<{}> = () => { return ( - -
-
- +
+
+ +
+
+
+
-
-
- -
-
- {props.children} -
+
+
- +
); }; diff --git a/frontEnd/src/config.ts b/frontEnd/src/config.ts index fce69ad..fd74269 100644 --- a/frontEnd/src/config.ts +++ b/frontEnd/src/config.ts @@ -1,4 +1,4 @@ -export const ServerUrl = "http://localhost:9002"; +export const ServerUrl = ""; -export const debounceWait = 1000; \ No newline at end of file +export const debounceWait = 1000; diff --git a/frontEnd/src/page/LogPage.tsx b/frontEnd/src/page/LogPage.tsx index 1f3e6e4..365a03d 100644 --- a/frontEnd/src/page/LogPage.tsx +++ b/frontEnd/src/page/LogPage.tsx @@ -51,6 +51,7 @@ import ChatMainComponent, { } from "src/component/chat/ChatMainComponent"; import { LinkOutlined, DisconnectOutlined } from "@ant-design/icons"; import { red, green } from "@ant-design/colors"; +import {useNavigate} from "react-router-dom"; function onLogsInsert( insertLog: LogM, @@ -134,6 +135,7 @@ const LogPage: React.FC = () => { const currentProject = projectState.projectList[projectState.curProjectIndex]; const [socketInstance, setSocketInstance] = useState(null); const [logs, setLogs] = useImmer>([]); + const navigate = useNavigate(); const expectationState = useAppSelector((state) => state.expectation); const getLogViewQuery = useQuery([currentProject.id], () => { @@ -217,7 +219,7 @@ const LogPage: React.FC = () => { ) .filter((item, index) => defaultColumnVisible[index]) .concat(customColumns) - .concat(getConfigColumn(dispatch)); + .concat(getConfigColumn(dispatch,navigate,currentProject.id)); updateLogColumn(newLogColumn); }, [ tableColumns, diff --git a/frontEnd/src/page/LogPageColumn.tsx b/frontEnd/src/page/LogPageColumn.tsx index d821724..36f3ef5 100644 --- a/frontEnd/src/page/LogPageColumn.tsx +++ b/frontEnd/src/page/LogPageColumn.tsx @@ -5,6 +5,7 @@ import { DeleteOutlined, FilterOutlined, MessageOutlined, + FileSearchOutlined, } from "@ant-design/icons"; import { createSimpleFilter, FilterType, LogM } from "livemock-core/struct/log"; import { Dispatch, useState } from "react"; @@ -29,7 +30,7 @@ import mStyle from "./LogPageColumn.module.scss"; import { ReactComponent as Equalizer } from "../svg/equalizer.svg"; import { ReactComponent as Eye } from "../svg/eye.svg"; import { ReactComponent as EyeBlocked } from "../svg/eye-blocked.svg"; -import _ from "lodash"; +import _, { after } from "lodash"; import ReactJson from "react-json-view"; import { v4 as uuId } from "uuid"; import TextColumn from "../component/table/TextColumn"; @@ -37,14 +38,33 @@ import { addLogFilterReq } from "../server/logFilterServer"; import { toastPromise } from "../component/common"; import { ExpectationM } from "livemock-core/struct/expectation"; import ExpectationBriefComponent from "../component/log/ExpectationBriefComponent"; +import { NavigateFunction } from "react-router-dom"; -export function getConfigColumn(dispatch: Dispatch) { +export function getConfigColumn( + dispatch: Dispatch, + navigate: NavigateFunction, + projectId:string, +) { return [ { dataIndex: "config", key: "config", width: "100px", - render: () =>
, + render: (text: string, record: LogM) => ( +
+
+ ), title: () => { return (
@@ -61,7 +81,7 @@ export function getConfigColumn(dispatch: Dispatch) { path: "", displayType: ColumnDisplayType.TEXT, visible: true, - }) + }), ); }} type={"text"} @@ -91,7 +111,7 @@ export function getDefaultColumn( refreshLogList: () => void, expectationMap: { [key: string]: ExpectationM; - } + }, ): ColumnsType { const res = [ { @@ -306,7 +326,7 @@ export function getDefaultColumnTitles() { function getDefaultColumnHead( name: string, dispatch: Dispatch, - index: number + index: number, ) { //const dispatch = useDispatch(); @@ -323,7 +343,7 @@ function getDefaultColumnHead( setDefaultColumnVisible({ index, visible: false, - }) + }), ); }} > @@ -385,7 +405,7 @@ const CustomColumnHead = ({ modifyTableColumn({ ...item, visible: false, - }) + }), ); }} > @@ -418,7 +438,7 @@ const CustomColumnHead = ({ export function getCustomColumn( items: Array, dispatch: Dispatch, - mode: "light" | "dark" + mode: "light" | "dark", ) { let res: ColumnType[] = []; items.forEach((item) => { @@ -429,7 +449,7 @@ export function getCustomColumn( export function transferColumn( item: TableColumnItem, dispatch: Dispatch, - mode: "light" | "dark" + mode: "light" | "dark", ): ColumnType { return { title: ( diff --git a/frontEnd/src/page/RequestLogDetail/RequestBodyCard.tsx b/frontEnd/src/page/RequestLogDetail/RequestBodyCard.tsx new file mode 100644 index 0000000..0e0d116 --- /dev/null +++ b/frontEnd/src/page/RequestLogDetail/RequestBodyCard.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import mStyle from "./RequestLogDetailPage.module.scss"; + +const RequestBodyCard: React.FunctionComponent<{ + body: any; +}> = ({ body }) => { + return ( +
+
Request Body
+
+
{JSON.stringify(body, null, 2)}
+
+
+ ); +}; + +export default RequestBodyCard; diff --git a/frontEnd/src/page/RequestLogDetail/RequestHeadersCard.tsx b/frontEnd/src/page/RequestLogDetail/RequestHeadersCard.tsx new file mode 100644 index 0000000..711e771 --- /dev/null +++ b/frontEnd/src/page/RequestLogDetail/RequestHeadersCard.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import mStyle from "./RequestLogDetailPage.module.scss"; + +const RequestHeadersCard: React.FunctionComponent<{ + headers: { + [key: string]: string | null | undefined; + }; +}> = ({ headers }) => { + return ( +
+
Request Headers
+
+ {Object.keys(headers).map((key: string) => { + return ( +
+
{key}
+
{headers[key] ?? ''}
+
+ ); + })} +
+
+ ); +}; + +export default RequestHeadersCard; diff --git a/frontEnd/src/page/RequestLogDetail/RequestLogDetailPage.module.scss b/frontEnd/src/page/RequestLogDetail/RequestLogDetailPage.module.scss new file mode 100644 index 0000000..fa5cc0c --- /dev/null +++ b/frontEnd/src/page/RequestLogDetail/RequestLogDetailPage.module.scss @@ -0,0 +1,114 @@ +.req_log_detail { + .req_tile { + color: #fff; + background-color: rgb(37, 99, 235); + padding: 2rem; + display: flex; + align-items: center; + justify-content: space-between; + } + .til_left { + display: flex; + align-items: center; + } + .til_right { + display: flex; + align-items: center; + } + .req_method { + font-size: 2.2rem; + font-weight: bold; + line-height: 3.5rem; + margin-right: 2rem; + } + .req_path { + font-size: 2.2rem; + line-height: 3.5rem; + } + .req_time { + font-size: 1.8rem; + line-height: 2.5rem; + margin-right: 2rem; + } + .req_status { + font-size: 1.8rem; + line-height: 2.5rem; + padding: .5rem 2rem; + border-radius: 9.9rem; + } + + .req_status_processing{ + background-color: #ff7a45; + } + .req_status_error{ + background-color: #ff4d4f; + } + .req_status_success{ + // background-color: #52c41a; + background-color: rgb(16, 185, 129); + } + + .req_content { + display: grid; + padding: 2rem; + gap: 2rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + background:#fff; + + .request_col { + } + .response_col { + } + .card { + padding: 2rem; + background-color: rgb(249, 250, 251); + border-radius: 1rem; + color: #000; + } + .card_til { + font-size: 2.2rem; + line-height: 3.5rem; + display: flex; + align-items: center; + margin-bottom: 1.5rem; + font-weight: 600; + } + .card_content { + } + .property_row { + font-size: 1.6rem; + line-height: 2.2rem; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 1rem; + margin-bottom: 1rem; + word-break: break-all; + .pro_label { + color: rgb(75 85 99); + } + .pro_value { + } + } + } + + .json_pre { + font-size: 1.4rem; + line-height: 2.2rem; + padding: 2rem; + white-space: pre-wrap; + color: rgb(52 211 153); + background-color: rgb(17 24 39); + border-radius: 1rem; + } +} + +html{ + font-size: 8px; + @media (min-width: 768px) { + font-size: 10px; + } + @media (min-width: 1024px) { + font-size: 12px; + } +} + diff --git a/frontEnd/src/page/RequestLogDetail/RequestLogDetailPage.tsx b/frontEnd/src/page/RequestLogDetail/RequestLogDetailPage.tsx new file mode 100644 index 0000000..04c1a3f --- /dev/null +++ b/frontEnd/src/page/RequestLogDetail/RequestLogDetailPage.tsx @@ -0,0 +1,106 @@ +import mStyle from "./RequestLogDetailPage.module.scss"; +import RequestHeadersCard from "./RequestHeadersCard"; +import RequestBodyCard from "./RequestBodyCard"; +import { useLocation, useParams, useSearchParams } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; +import { getRequestLogDetail } from "../../server/logServer"; +import { useAppSelector } from "../../store"; +import { ClockCircleOutlined } from "@ant-design/icons"; +import RequestQueryCard from "./RequestQueryCard"; + + +function isEmptyObject(obj: any): boolean { + if(!obj){ + return true; + } + return Object.keys(obj).length === 0; +} + +const RequestLogDetailPage = () => { + const params = useParams<{ + logId: string; + }>(); + const [searchParam, setSearchParam] = useSearchParams(); + const projectId = searchParam.get("projectId"); + if (projectId === null) { + throw new Error("No projectId provided"); + } + const getLogDetailQuery = useQuery([params.logId], () => { + if (!params.logId) { + throw new Error("No log detail found."); + } + return getRequestLogDetail(parseInt(params.logId), { + projectId: projectId, + }); + }); + + function getStatusClass(reqCode: number | undefined) { + if (!reqCode) { + return ""; + } + if (reqCode < 200) { + return mStyle.req_status_processing; + } else if (reqCode < 300) { + return mStyle.req_status_success; + } else if (reqCode < 400) { + return mStyle.req_status_processing; + } else { + return mStyle.req_status_error; + } + } + + return ( +
+
+
+
+ {getLogDetailQuery.data?.logItem.req?.method} +
+
+ {getLogDetailQuery.data?.logItem.req?.path} +
+
+
+
+ 2025-01-10 12:00:00 +
+
+ {getLogDetailQuery.data?.logItem.res?.status} +
+
+
+ +
+
+ + {!isEmptyObject(getLogDetailQuery.data?.logItem.req?.query) && ( + <> +
+ + + )} +
+ +
+
+ +
+ +
+
+
+ ); +}; + +export default RequestLogDetailPage; diff --git a/frontEnd/src/page/RequestLogDetail/RequestQueryCard.tsx b/frontEnd/src/page/RequestLogDetail/RequestQueryCard.tsx new file mode 100644 index 0000000..fa7bed1 --- /dev/null +++ b/frontEnd/src/page/RequestLogDetail/RequestQueryCard.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import mStyle from "./RequestLogDetailPage.module.scss"; + +const RequestBodyCard: React.FunctionComponent<{ + query: any; +}> = ({ query }) => { + return ( +
+
Request Query
+
+
{JSON.stringify(query, null, 2)}
+
+
+ ); +}; + +export default RequestBodyCard; diff --git a/frontEnd/src/server/logServer.ts b/frontEnd/src/server/logServer.ts index 1c4aa6d..861afbd 100644 --- a/frontEnd/src/server/logServer.ts +++ b/frontEnd/src/server/logServer.ts @@ -1,5 +1,7 @@ import { DeleteAllRequestLogsReqQuery, + GetLogDetailReqQuery, + GetLogDetailResponse, ListLogViewLogsReqQuery, ListLogViewReqQuery, } from "livemock-core/struct/params/LogParams"; @@ -36,3 +38,13 @@ export async function deleteAllRequestLogs( const response = await superagent.delete(`${ServerUrl}/log`).query(query); return response.body; } + +export async function getRequestLogDetail( + logId: number, + query: GetLogDetailReqQuery, +): Promise { + const response = await superagent + .get(`${ServerUrl}/log/detail/${logId}`) + .query(query); + return response.body; +} diff --git a/package.json b/package.json index eb66388..870c73b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "web-dev": "concurrently \"npm run back-end-dev\" \"npm run front-end-dev\"", "desktop-dev": "yarn workspace livemock-core build && yarn workspace livemock dev", "web-build": "yarn workspace livemock-core build && yarn workspace front-end build", - "web-start": "yarn workspace back-end start" + "web-start": "yarn workspace back-end start", + "test": "yarn workspaces foreach run test", + "test:back-end": "yarn workspace back-end test", + "test:core": "yarn workspace livemock-core test", + "test:watch": "yarn workspaces foreach run test --watch" }, "repository": { "type": "git", @@ -46,5 +50,5 @@ "express": "4.18.2", "uuid": "9.0.1" }, - "packageManager": "yarn@4.5.2" + "packageManager": "yarn@4.5.3" }