├── .github
├── release-drafter.yml
└── workflows
│ ├── release-ci.yml
│ └── test-ci.yml
├── .gitignore
├── Dockerfile
├── Dockerfile-nginx
├── README.md
├── logo.jpeg
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.jsx
├── api
│ ├── ai.jsx
│ ├── auditLog.jsx
│ ├── cloudwatch.jsx
│ ├── dashboard.jsx
│ ├── datasource.jsx
│ ├── duty.jsx
│ ├── event.jsx
│ ├── faultCenter.jsx
│ ├── kubernetes.jsx
│ ├── notice.jsx
│ ├── noticeTmpl.jsx
│ ├── other.jsx
│ ├── permissions.jsx
│ ├── probing.jsx
│ ├── role.jsx
│ ├── rule.jsx
│ ├── ruleTmpl.jsx
│ ├── settings.jsx
│ ├── silence.jsx
│ ├── subscribe.jsx
│ ├── tenant.jsx
│ └── user.jsx
├── components
│ ├── context
│ │ └── index.jsx
│ ├── index.css
│ ├── index.jsx
│ └── sider
│ │ └── index.jsx
├── context
│ └── RuleContext.js
├── img
│ ├── 701986.svg
│ ├── WatchAlert.png
│ ├── github_logo.png
│ └── logo.jpeg
├── index.css
├── index.js
├── pages
│ ├── alert
│ │ ├── assets
│ │ │ ├── event.svg
│ │ │ ├── log.svg
│ │ │ ├── metric.svg
│ │ │ └── trace.svg
│ │ ├── preview
│ │ │ └── searchViewLogs.jsx
│ │ ├── rule
│ │ │ ├── create.jsx
│ │ │ ├── img
│ │ │ │ ├── AWSlogo.svg
│ │ │ │ ├── ElasticSearch.svg
│ │ │ │ ├── Kubernetes.svg
│ │ │ │ ├── L.svg
│ │ │ │ ├── Prometheus.svg
│ │ │ │ ├── alicloud.svg
│ │ │ │ ├── jaeger.svg
│ │ │ │ ├── victorialogs.svg
│ │ │ │ └── victoriametrics.svg
│ │ │ ├── index.css
│ │ │ └── index.jsx
│ │ ├── ruleGroup
│ │ │ ├── AlertRuleGroupCreateModal.jsx
│ │ │ └── index.jsx
│ │ ├── tmpl
│ │ │ ├── RuleTemplateCreateModal.jsx
│ │ │ └── index.jsx
│ │ └── tmplGroup
│ │ │ ├── RuleTemplateGroupCreateModal.jsx
│ │ │ └── index.jsx
│ ├── audit
│ │ └── index.jsx
│ ├── chart
│ │ └── noticeMetricChart.jsx
│ ├── dashboards
│ │ ├── dashboard
│ │ │ ├── iframe.jsx
│ │ │ └── index.jsx
│ │ └── folder
│ │ │ ├── create.jsx
│ │ │ └── index.jsx
│ ├── datasources
│ │ ├── DatasourceCreateModal.jsx
│ │ ├── index.css
│ │ └── index.jsx
│ ├── duty
│ │ ├── DutyManageCreateModal.jsx
│ │ ├── calendar
│ │ │ ├── CreateCalendar.jsx
│ │ │ ├── UpdateCalendar.jsx
│ │ │ ├── index.css
│ │ │ └── index.jsx
│ │ └── index.jsx
│ ├── event
│ │ ├── currentEvent.jsx
│ │ ├── historyEvent.jsx
│ │ ├── index.css
│ │ └── tmpl
│ │ │ └── index.jsx
│ ├── faultCenter
│ │ ├── create.jsx
│ │ ├── detail.jsx
│ │ ├── index.css
│ │ ├── index.jsx
│ │ ├── notify.jsx
│ │ └── upgrade.jsx
│ ├── global.css
│ ├── home.jsx
│ ├── login.jsx
│ ├── members
│ │ ├── role
│ │ │ ├── UserRoleCreateModal.jsx
│ │ │ └── index.jsx
│ │ └── user
│ │ │ ├── UserChangePass.jsx
│ │ │ ├── UserCreateModal.jsx
│ │ │ └── index.jsx
│ ├── notice
│ │ ├── NoticeObjectCreateModal.jsx
│ │ ├── history.jsx
│ │ ├── img
│ │ │ ├── Email.svg
│ │ │ ├── customhook.svg
│ │ │ ├── dingding.svg
│ │ │ ├── feishu.svg
│ │ │ └── qywechat.svg
│ │ ├── index.jsx
│ │ ├── notification-type-icon.jsx
│ │ └── tmpl
│ │ │ ├── NoticeTemplateCreateModal.jsx
│ │ │ └── index.jsx
│ ├── probing
│ │ ├── create.jsx
│ │ ├── detail.jsx
│ │ ├── index.css
│ │ ├── index.jsx
│ │ └── once.jsx
│ ├── profile
│ │ └── index.jsx
│ ├── promethues
│ │ ├── index.css
│ │ └── index.jsx
│ ├── settings
│ │ ├── index.css
│ │ └── index.jsx
│ ├── silence
│ │ ├── SilenceRuleCreateModal.jsx
│ │ └── index.jsx
│ ├── subscribe
│ │ ├── create.jsx
│ │ └── index.jsx
│ └── tenant
│ │ ├── CreateTenant.jsx
│ │ ├── detail.jsx
│ │ ├── index.css
│ │ ├── index.jsx
│ │ ├── quota.jsx
│ │ ├── security.jsx
│ │ └── users.jsx
├── routes
│ └── index.jsx
└── utils
│ ├── Auth.jsx
│ ├── Error.jsx
│ ├── JsonTable.jsx
│ ├── MarkdownRenderer.css
│ ├── MarkdownRenderer.jsx
│ ├── See.jsx
│ ├── VSCodeEditor.jsx
│ ├── copyToClipboard.jsx
│ ├── exportAlarmRecordToHTML.jsx
│ └── http.jsx
├── tailwind.config.js
├── w8t.conf
└── yarn.lock
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - ignore-for-release
5 | categories:
6 | - title: '🚀 Features'
7 | labels:
8 | - 'feature'
9 | - title: '🐛 Bug Fixes'
10 | labels:
11 | - 'bug'
12 | - title: 📝 Documentation updates
13 | labels:
14 | - documentation
15 | - title: 👻 Maintenance
16 | labels:
17 | - dependencies
18 | - title: 🚦 Tests
19 | labels:
20 | - tests
21 | - title: Other Changes
22 | labels:
23 | - "*"
--------------------------------------------------------------------------------
/.github/workflows/release-ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | tags:
6 | - v*
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v3
15 |
16 | - name: Set up QEMU
17 | uses: docker/setup-qemu-action@v3
18 |
19 | - name: Inject slug/short variables
20 | uses: rlespinasse/github-slug-action@v4
21 |
22 | - name: Set up Docker Buildx
23 | id: buildx
24 | uses: docker/setup-buildx-action@v3
25 | with:
26 | driver: docker-container
27 |
28 | - name: Available platforms
29 | run: echo ${{ steps.buildx.outputs.platforms }}
30 |
31 | - name: Login to Docker Hub
32 | uses: docker/login-action@v3
33 | with:
34 | username: ${{ secrets.DOCKERHUB_USER }}
35 | password: ${{ secrets.DOCKERHUB_TOKEN }}
36 |
37 | - name: Set env variables
38 | id: set_env
39 | run: |
40 | echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
41 |
42 | - name: Build and push
43 | uses: docker/build-push-action@v6
44 | with:
45 | context: .
46 | file: ./Dockerfile-nginx
47 | platforms: linux/amd64,linux/arm64
48 | push: true
49 | build-args: |
50 | VERSION=${{ env.TAG_NAME }}
51 | tags: |
52 | cairry/watchalert-web:latest
53 | cairry/watchalert-web:${{ env.TAG_NAME }}
--------------------------------------------------------------------------------
/.github/workflows/test-ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*/*'
7 | - 'master'
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v3
16 |
17 | - name: Set up QEMU
18 | uses: docker/setup-qemu-action@v3
19 |
20 | - name: Inject slug/short variables
21 | uses: rlespinasse/github-slug-action@v4
22 |
23 | - name: Set up Docker Buildx
24 | id: buildx
25 | uses: docker/setup-buildx-action@v3
26 | with:
27 | driver: docker-container
28 |
29 | - name: Available platforms
30 | run: echo ${{ steps.buildx.outputs.platforms }}
31 |
32 | - name: Login to Docker Hub
33 | uses: docker/login-action@v3
34 | with:
35 | username: ${{ secrets.DOCKERHUB_USER }}
36 | password: ${{ secrets.DOCKERHUB_TOKEN }}
37 |
38 | - name: Set env variables
39 | id: set_env
40 | run: |
41 | echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
42 |
43 | - name: Build and push
44 | uses: docker/build-push-action@v6.15.0
45 | with:
46 | context: .
47 | file: ./Dockerfile-nginx
48 | platforms: linux/amd64,linux/arm64
49 | push: true
50 | build-args: |
51 | VERSION=${{ env.DATE }}
52 | tags: |
53 | cairry/watchalert-web:${{ env.BRANCH_NAME }}
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18.15.0-alpine3.17 as build
2 |
3 | RUN mkdir /app
4 |
5 | COPY . /app
6 |
7 | WORKDIR /app
8 |
9 | RUN yarn config set registry https://registry.npmmirror.com && \
10 | yarn install
11 |
12 | EXPOSE 3000
13 |
14 | # 如果需要指定后端服务端口号,例如:REACT_APP_BACKEND_PORT=9002 yarn start。
15 | CMD [ "yarn", "start" ]
16 |
17 | #FROM node:18.15.0-alpine3.17 as build
18 | #
19 | #RUN mkdir /app
20 | #
21 | #COPY . /app
22 | #
23 | #WORKDIR /app
24 | #
25 | #RUN yarn config set registry https://registry.npmmirror.com && \
26 | # yarn build
27 | #
28 | #FROM firesh/nginx-lua:alpine-3.18 as release
29 | #
30 | #COPY --from=build /app/build/ /app/
31 | #
32 | #COPY ./w8t.conf /etc/nginx/conf.d
33 | #
34 | #WORKDIR /etc/nginx/
35 | #
36 | #EXPOSE 80
37 | #
38 | #CMD ["nginx","-g","daemon off;"]
39 |
--------------------------------------------------------------------------------
/Dockerfile-nginx:
--------------------------------------------------------------------------------
1 | FROM node:18.20.3-alpine3.20 as build
2 |
3 | RUN mkdir /app
4 |
5 | COPY . /app
6 |
7 | WORKDIR /app
8 |
9 | RUN yarn config set registry https://registry.npmmirror.com && \
10 | yarn install && \
11 | yarn build && \
12 | tar zcf build.tar.gz ./build
13 |
14 | FROM nginx:1.18.0 as release
15 |
16 | RUN mkdir /app
17 |
18 | COPY --from=build /app/build.tar.gz /app
19 |
20 | RUN tar zxf /app/build.tar.gz -C /app && \
21 | mv /app/build/* /app && \
22 | rm -rf /app/build* && \
23 | rm -rf /etc/nginx/conf.d/default.conf
24 |
25 | COPY ./w8t.conf /etc/nginx/conf.d
26 |
27 | WORKDIR /etc/nginx/
28 |
29 | EXPOSE 80
30 |
31 | CMD ["nginx","-g","daemon off;"]
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opsre/WatchAlert-web/85e8f39a1d4c2c8884eaee4bd2257b357a0f5716/logo.jpeg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "watchalert-web",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^6.0.0",
7 | "@ant-design/x": "^1.0.5",
8 | "@codemirror/autocomplete": "^0.18.3",
9 | "@codemirror/closebrackets": "^0.18.0",
10 | "@codemirror/commands": "^0.18.0",
11 | "@codemirror/comment": "^0.18.0",
12 | "@codemirror/highlight": "^0.18.3",
13 | "@codemirror/history": "^0.18.0",
14 | "@codemirror/language": "^0.18.0",
15 | "@codemirror/lint": "^0.18.1",
16 | "@codemirror/matchbrackets": "^0.18.0",
17 | "@codemirror/search": "^0.18.2",
18 | "@codemirror/state": "^0.18.2",
19 | "@codemirror/view": "^0.18.3",
20 | "@heroui/react": "^2.7.8",
21 | "@heroui/toast": "^2.0.9",
22 | "@monaco-editor/react": "^4.7.0",
23 | "@tailwindcss/postcss": "^4.1.6",
24 | "antd": "5.13.2",
25 | "axios": "^0.26.0",
26 | "codemirror": "^6.0.1",
27 | "codemirror-promql": "0.17.0",
28 | "components": "^0.1.0",
29 | "dayjs": "^1.11.13",
30 | "echarts": "^5.6.0",
31 | "echarts-for-react": "^3.0.2",
32 | "framer-motion": "^12.11.0",
33 | "github-markdown-css": "^5.8.1",
34 | "lodash-es": "^4.17.21",
35 | "lucide-react": "^0.510.0",
36 | "moment": "^2.30.1",
37 | "monaco-editor": "^0.52.2",
38 | "next": "^15.1.0",
39 | "react": "^18.2.0",
40 | "react-beautiful-dnd": "^13.1.1",
41 | "react-dom": "^18.2.0",
42 | "react-fast-marquee": "^1.6.5",
43 | "react-helmet": "^6.1.0",
44 | "react-is": "^18.2.0",
45 | "react-json-editor-ajrm": "^2.5.14",
46 | "react-json-view": "^1.21.3",
47 | "react-markdown": "^10.0.1",
48 | "react-router-dom": "^6.22.1",
49 | "react-scripts": "5.0.0",
50 | "react-syntax-highlighter": "^15.6.1",
51 | "react-toastify": "^10.0.4",
52 | "reactstrap": "^8.0.1",
53 | "recharts": "^2.15.3",
54 | "tailwind": "^4.0.0",
55 | "tailwindcss": "3.4.16"
56 | },
57 | "scripts": {
58 | "start": "react-scripts start",
59 | "build": "react-scripts build",
60 | "test": "react-scripts test",
61 | "eject": "react-scripts eject"
62 | },
63 | "eslintConfig": {
64 | "extends": [
65 | "react-app",
66 | "react-app/jest"
67 | ]
68 | },
69 | "browserslist": {
70 | "production": [
71 | ">0.2%",
72 | "not dead",
73 | "not op_mini all"
74 | ],
75 | "development": [
76 | "last 1 chrome version",
77 | "last 1 firefox version",
78 | "last 1 safari version"
79 | ]
80 | },
81 | "devDependencies": {
82 | "autoprefixer": "^10.4.21",
83 | "json-server": "^0.17.0",
84 | "postcss": "^8.5.3",
85 | "tailwindcss": "^4.1.6"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opsre/WatchAlert-web/85e8f39a1d4c2c8884eaee4bd2257b357a0f5716/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opsre/WatchAlert-web/85e8f39a1d4c2c8884eaee4bd2257b357a0f5716/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opsre/WatchAlert-web/85e8f39a1d4c2c8884eaee4bd2257b357a0f5716/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ConfigProvider, theme } from 'antd';
3 | import { Helmet } from 'react-helmet';
4 | import routes from './routes';
5 | import { useRoutes } from 'react-router-dom';
6 | import './index.css'
7 | import { ContextProvider } from './context/RuleContext';
8 |
9 | export default function App() {
10 | const element = useRoutes(routes);
11 | const title = "WatchAlert";
12 |
13 | return (
14 |
15 |
16 |
17 | {title}
18 |
19 | {element}
20 |
21 |
22 | );
23 | }
--------------------------------------------------------------------------------
/src/api/ai.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | export async function ReqAiAnalyze(params) {
5 | try {
6 | const response = await http('post', `/api/w8t/ai/chat`,params);
7 | return response;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: `Ai 分析失败: ${error.message}`,
12 | });
13 | return error
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/api/auditLog.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function listAuditLog(params) {
5 | try {
6 | const queryString = Object.keys(params)
7 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
8 | .filter(Boolean)
9 | .join('&');
10 | const res = await http('get', `/api/w8t/auditLog/listAuditLog?${queryString}`);
11 | return res;
12 | } catch (error) {
13 | message.open({
14 | type: 'error',
15 | content: '审计日志列表获取失败',
16 | });
17 | return error
18 | }
19 | }
20 |
21 | async function searchAuditLog(params) {
22 | try {
23 | const queryString = Object.keys(params)
24 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
25 | .filter(Boolean)
26 | .join('&');
27 |
28 | const res = await http('get', `/api/w8t/auditLog/searchAuditLog?${queryString}`);
29 | return res;
30 | } catch (error) {
31 | message.open({
32 | type: 'error',
33 | content: '日志审计列表查询失败',
34 | });
35 | return error
36 | }
37 | }
38 |
39 | export {
40 | listAuditLog,
41 | searchAuditLog
42 | }
--------------------------------------------------------------------------------
/src/api/cloudwatch.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getMetricTypes() {
5 | try {
6 | const res = await http('get', '/api/w8t/community/cloudwatch/metricTypes');
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '指标类型获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function getMetricNames(params) {
18 | try {
19 | const res = await http('get', '/api/w8t/community/cloudwatch/metricNames?metricType='+params.metricType);
20 | return res;
21 | } catch (error) {
22 | message.open({
23 | type: 'error',
24 | content: '指标获取失败',
25 | });
26 | return error
27 | }
28 | }
29 |
30 | async function getStatistics() {
31 | try {
32 | const res = await http('get', '/api/w8t/community/cloudwatch/statistics');
33 | return res;
34 | } catch (error) {
35 | message.open({
36 | type: 'error',
37 | content: '统计类型获取失败',
38 | });
39 | return error
40 | }
41 | }
42 |
43 | async function getDimensions(params) {
44 | try {
45 | const res = await http('get', '/api/w8t/community/cloudwatch/dimensions?metricType='+params.metricType);
46 | return res;
47 | } catch (error) {
48 | message.open({
49 | type: 'error',
50 | content: '端点类型获取失败',
51 | });
52 | return error
53 | }
54 | }
55 |
56 | async function getRdsInstances(params) {
57 | try {
58 | const res = await http('get', '/api/w8t/community/rds/instances?datasourceId='+params.datasourceId);
59 | return res;
60 | } catch (error) {
61 | message.open({
62 | type: 'error',
63 | content: '端点实例获取失败',
64 | });
65 | return error
66 | }
67 | }
68 |
69 | async function getRdsClusters(params) {
70 | try {
71 | const res = await http('get', '/api/w8t/community/rds/clusters?datasourceId='+params.datasourceId);
72 | return res;
73 | } catch (error) {
74 | message.open({
75 | type: 'error',
76 | content: '端点集群获取失败',
77 | });
78 | return error
79 | }
80 | }
81 |
82 | export {
83 | getMetricTypes,
84 | getMetricNames,
85 | getStatistics,
86 | getDimensions,
87 | getRdsInstances,
88 | getRdsClusters
89 | }
--------------------------------------------------------------------------------
/src/api/dashboard.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getFolderList(params?: any) {
5 | try {
6 | const res = await http('get', '/api/w8t/dashboard/listFolder', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '文件夹列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function getFolderInfo(params?: any) {
18 | try {
19 | const res = await http('get', '/api/w8t/dashboard/getFolder', params);
20 | return res;
21 | } catch (error) {
22 | message.open({
23 | type: 'error',
24 | content: '文件夹信息获取失败',
25 | });
26 | return error
27 | }
28 | }
29 |
30 | async function getDashboardList(params) {
31 | try {
32 | const res = await http('get', '/api/w8t/dashboard/listDashboard', params);
33 | return res;
34 | } catch (error) {
35 | message.open({
36 | type: 'error',
37 | content: '仪表盘列表获取失败',
38 | });
39 | return error
40 | }
41 | }
42 |
43 | async function getDashboardData(params) {
44 | const queryString = Object.keys(params)
45 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
46 | .filter(Boolean)
47 | .join('&');
48 |
49 | try {
50 | const res = await http('get', `/api/w8t/dashboard/getDashboard?${queryString}`);
51 | return res;
52 | } catch (error) {
53 | message.open({
54 | type: 'error',
55 | content: '仪表盘数据获取失败',
56 | });
57 | return error
58 | }
59 | }
60 |
61 | async function createDashboard(params) {
62 | try {
63 | const res = await http('post', '/api/w8t/dashboard/createDashboard', params);
64 | message.open({
65 | type: 'success',
66 | content: '仪表盘创建成功',
67 | });
68 | return res;
69 | } catch (error) {
70 | message.open({
71 | type: 'error',
72 | content: '仪表盘创建失败',
73 | });
74 | return error
75 | }
76 | }
77 |
78 | async function updateDashboard(params) {
79 | try {
80 | const res = await http('post', '/api/w8t/dashboard/updateDashboard', params);
81 | message.open({
82 | type: 'success',
83 | content: '仪表盘更新成功',
84 | });
85 | return res;
86 | } catch (error) {
87 | message.open({
88 | type: 'error',
89 | content: '仪表盘更新失败',
90 | });
91 | return error
92 | }
93 | }
94 |
95 | async function deleteDashboard(params) {
96 | try {
97 | const res = await http('post', '/api/w8t/dashboard/deleteDashboard', params);
98 | message.open({
99 | type: 'success',
100 | content: '仪表盘删除成功',
101 | });
102 | return res;
103 | } catch (error) {
104 | message.open({
105 | type: 'error',
106 | content: '仪表盘删除失败',
107 | });
108 | return error
109 | }
110 | }
111 |
112 | async function searchDashboard(params) {
113 | const queryString = Object.keys(params)
114 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
115 | .filter(Boolean)
116 | .join('&');
117 |
118 | try {
119 | const res = await http('get', `/api/w8t/dashboard/searchDashboard?${queryString}`);
120 | return res;
121 | } catch (error) {
122 | message.open({
123 | type: 'error',
124 | content: '仪表盘数据获取失败',
125 | });
126 | return error
127 | }
128 | }
129 |
130 | async function createDashboardFolder(params) {
131 | try {
132 | const res = await http('post', '/api/w8t/dashboard/createFolder', params);
133 | return res;
134 | } catch (error) {
135 | message.open({
136 | type: 'error',
137 | content: '文件夹创建失败',
138 | });
139 | return error
140 | }
141 | }
142 |
143 | async function deleteDashboardFolder(params) {
144 | try {
145 | const res = await http('post', '/api/w8t/dashboard/deleteFolder', params);
146 | message.open({
147 | type: 'success',
148 | content: '文件夹删除成功',
149 | });
150 | return res;
151 | } catch (error) {
152 | message.open({
153 | type: 'error',
154 | content: '文件夹删除失败',
155 | });
156 | return error
157 | }
158 | }
159 |
160 | async function updateDashboardFolder(params) {
161 | try {
162 | const res = await http('post', '/api/w8t/dashboard/updateFolder', params);
163 | message.open({
164 | type: 'success',
165 | content: '文件夹更新成功',
166 | });
167 | return res;
168 | } catch (error) {
169 | message.open({
170 | type: 'error',
171 | content: '文件夹更新失败',
172 | });
173 | return error
174 | }
175 | }
176 |
177 | async function getGrafanaDashboardList(params?: any) {
178 | try {
179 | const res = await http('get', '/api/w8t/dashboard/listGrafanaDashboards', params);
180 | return res;
181 | } catch (error) {
182 | message.open({
183 | type: 'error',
184 | content: 'Grafana仪表盘列表获取失败',
185 | });
186 | return error
187 | }
188 | }
189 |
190 | async function getDashboardFullUrl(params?: any) {
191 | try {
192 | const res = await http('get', '/api/w8t/dashboard/getDashboardFullUrl', params);
193 | return res;
194 | } catch (error) {
195 | message.open({
196 | type: 'error',
197 | content: 'Grafana仪表盘完整URL获取失败',
198 | });
199 | return error
200 | }
201 | }
202 |
203 | export {
204 | getFolderList,
205 | getFolderInfo,
206 | getDashboardList,
207 | getDashboardData,
208 | createDashboard,
209 | updateDashboard,
210 | deleteDashboard,
211 | searchDashboard,
212 | createDashboardFolder,
213 | updateDashboardFolder,
214 | deleteDashboardFolder,
215 | getGrafanaDashboardList,
216 | getDashboardFullUrl,
217 | }
--------------------------------------------------------------------------------
/src/api/datasource.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getDatasourceList(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/datasource/dataSourceList', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '数据源列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function searchDatasource(params) {
18 | try {
19 | const queryString = Object.keys(params)
20 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
21 | .filter(Boolean)
22 | .join('&');
23 | const res = await http('get', `/api/w8t/datasource/dataSourceSearch?${queryString}`);
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '数据源搜索失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function getDatasource(params) {
35 | try {
36 | const queryString = Object.keys(params)
37 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
38 | .filter(Boolean)
39 | .join('&');
40 | const res = await http('get', `/api/w8t/datasource/dataSourceGet?${queryString}`);
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '数据源搜索失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | async function createDatasource(params) {
52 | try {
53 | const res = await http('post', '/api/w8t/datasource/dataSourceCreate', params);
54 | message.open({
55 | type: 'success',
56 | content: '数据源创建成功',
57 | });
58 | return res;
59 | } catch (error) {
60 | message.open({
61 | type: 'error',
62 | content: '数据源创建失败',
63 | });
64 | return error
65 | }
66 | }
67 |
68 | async function updateDatasource(params) {
69 | try {
70 | const res = await http('post', '/api/w8t/datasource/dataSourceUpdate', params);
71 | message.open({
72 | type: 'success',
73 | content: '数据源更新成功',
74 | });
75 | return res;
76 | } catch (error) {
77 | message.open({
78 | type: 'error',
79 | content: '数据源更新失败',
80 | });
81 | return error
82 | }
83 | }
84 |
85 | async function deleteDatasource(params) {
86 | try {
87 | const res = await http('post', `/api/w8t/datasource/dataSourceDelete`, params);
88 | message.open({
89 | type: 'success',
90 | content: '数据源删除成功',
91 | });
92 | return res;
93 | } catch (error) {
94 | message.open({
95 | type: 'error',
96 | content: '数据源删除失败',
97 | });
98 | return error
99 | }
100 | }
101 |
102 | async function DatasourcePing(params) {
103 | try {
104 | const res = await http('post', `/api/w8t/datasource/dataSourcePing`, params);
105 | message.open({
106 | type: 'success',
107 | content: '数据源测试通过',
108 | });
109 | return res;
110 | } catch (error) {
111 | message.open({
112 | type: 'error',
113 | content: '数据源测试失败',
114 | });
115 | return error
116 | }
117 | }
118 |
119 | async function ElasticSearchData(params) {
120 | try {
121 | const res = await http('post', `/api/w8t/datasource/esSearch`, params);
122 | message.open({
123 | type: 'success',
124 | content: '查询ES内容成功',
125 | });
126 | return res;
127 | } catch (error) {
128 | message.open({
129 | type: 'error',
130 | content: '查询ES内容失败',
131 | });
132 | return error
133 | }
134 | }
135 |
136 | async function SearchViewLogsContent(params) {
137 | try {
138 | const res = await http('post', `/api/w8t/datasource/searchViewLogsContent`, params);
139 | return res;
140 | } catch (error) {
141 | message.open({
142 | type: 'error',
143 | content: '数据预览内容查询失败',
144 | });
145 | return error
146 | }
147 | }
148 |
149 | export {
150 | getDatasourceList,
151 | searchDatasource,
152 | createDatasource,
153 | updateDatasource,
154 | deleteDatasource,
155 | getDatasource,
156 | DatasourcePing,
157 | ElasticSearchData,
158 | SearchViewLogsContent
159 | }
--------------------------------------------------------------------------------
/src/api/duty.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getDutyManagerList(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/dutyManage/dutyManageList', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '值班列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function createDutyManager(params) {
18 | try {
19 | const res = await http('post', '/api/w8t/dutyManage/dutyManageCreate', params);
20 | message.open({
21 | type: 'success',
22 | content: '值班表创建成功',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '值班表创建失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function updateDutyManager(params) {
35 | try {
36 | const res = await http('post', '/api/w8t/dutyManage/dutyManageUpdate', params);
37 | message.open({
38 | type: 'success',
39 | content: '值班表更新成功',
40 | });
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '值班表更新失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | async function deleteDutyManager(params) {
52 | try {
53 | const res = await http('post', `/api/w8t/dutyManage/dutyManageDelete`, params);
54 | message.open({
55 | type: 'success',
56 | content: '值班表删除成功',
57 | });
58 | return res;
59 | } catch (error) {
60 | message.open({
61 | type: 'error',
62 | content: '值班表删除失败',
63 | });
64 | return error
65 | }
66 | }
67 |
68 | async function createCalendar(params) {
69 | try {
70 | const res = await http('post', '/api/w8t/calendar/calendarCreate', params);
71 | message.open({
72 | type: 'success',
73 | content: '日程表发布成功',
74 | });
75 | return res;
76 | } catch (error) {
77 | message.open({
78 | type: 'error',
79 | content: '日程表发布失败',
80 | });
81 | return error
82 | }
83 | }
84 |
85 | async function updateCalendar(params) {
86 | try {
87 | const res = await http('post', '/api/w8t/calendar/calendarUpdate', params);
88 | message.open({
89 | type: 'success',
90 | content: '日程表更新成功',
91 | });
92 | return res;
93 | } catch (error) {
94 | message.open({
95 | type: 'error',
96 | content: '日程表更新失败',
97 | });
98 | return error
99 | }
100 | }
101 |
102 | async function searchCalendar(params) {
103 | try {
104 | const res = await http('get', '/api/w8t/calendar/calendarSearch', params);
105 | return res;
106 | } catch (error) {
107 | message.open({
108 | type: 'error',
109 | content: '日程表查询失败',
110 | });
111 | return error
112 | }
113 | }
114 |
115 | async function GetCalendarUsers(params) {
116 | try {
117 | const res = await http('get', '/api/w8t/calendar/getCalendarUsers', params);
118 | return res;
119 | } catch (error) {
120 | message.open({
121 | type: 'error',
122 | content: '获取值班表用户列表失败',
123 | });
124 | return error
125 | }
126 | }
127 |
128 | export {
129 | getDutyManagerList,
130 | createDutyManager,
131 | updateDutyManager,
132 | deleteDutyManager,
133 | createCalendar,
134 | updateCalendar,
135 | searchCalendar,
136 | GetCalendarUsers
137 | }
--------------------------------------------------------------------------------
/src/api/event.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getCurEventList(params) {
5 | try {
6 | const queryString = Object.keys(params)
7 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
8 | .filter(Boolean)
9 | .join('&');
10 | const res = await http('get', `/api/w8t/event/curEvent?${queryString}`);
11 | return res;
12 | } catch (error) {
13 | message.open({
14 | type: 'error',
15 | content: '当前告警列表获取失败',
16 | });
17 | return error
18 | }
19 | }
20 |
21 | async function getHisEventList(params) {
22 | try {
23 | const queryString = Object.keys(params)
24 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
25 | .filter(Boolean)
26 | .join('&');
27 |
28 | const url = `/api/w8t/event/hisEvent?${queryString}`;
29 | const res = await http('get', url);
30 | return res;
31 | } catch (error) {
32 | message.open({
33 | type: 'error',
34 | content: '历史告警列表获取失败',
35 | });
36 | return error
37 | }
38 | }
39 |
40 | async function ProcessAlertEvent(params) {
41 | return await http('post', `/api/w8t/event/processAlertEvent`,params);
42 | }
43 |
44 | export {
45 | getCurEventList,
46 | getHisEventList,
47 | ProcessAlertEvent
48 | }
--------------------------------------------------------------------------------
/src/api/faultCenter.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function FaultCenterList(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/faultCenter/faultCenterList', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '故障中心列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function FaultCenterSearch(params) {
18 | try {
19 | const res = await http('get', '/api/w8t/faultCenter/faultCenterSearch', params);
20 | return res;
21 | } catch (error) {
22 | message.open({
23 | type: 'error',
24 | content: '故障中心详情获取失败',
25 | });
26 | return error
27 | }
28 | }
29 |
30 | async function FaultCenterCreate(params) {
31 | try {
32 | const res = await http('post', '/api/w8t/faultCenter/faultCenterCreate', params);
33 | message.open({
34 | type: 'success',
35 | content: '故障中心创建成功',
36 | });
37 | return res;
38 | } catch (error) {
39 | message.open({
40 | type: 'error',
41 | content: `故障中心创建失败`,
42 | });
43 | return error
44 | }
45 | }
46 |
47 | async function FaultCenterUpdate(params) {
48 | try {
49 | const res = await http('post', '/api/w8t/faultCenter/faultCenterUpdate', params);
50 | message.open({
51 | type: 'success',
52 | content: '故障中心更新成功',
53 | });
54 | return res;
55 | } catch (error) {
56 | message.open({
57 | type: 'error',
58 | content: `故障中心更新失败`,
59 | });
60 | return error
61 | }
62 | }
63 |
64 | async function FaultCenterDelete(params) {
65 | try {
66 | const res = await http('post', '/api/w8t/faultCenter/faultCenterDelete', params);
67 | message.open({
68 | type: 'success',
69 | content: '故障中心删除成功',
70 | });
71 | return res;
72 | } catch (error) {
73 | message.open({
74 | type: 'error',
75 | content: `故障中心删除失败`,
76 | });
77 | return error
78 | }
79 | }
80 |
81 | async function FaultCenterReset(params) {
82 | try {
83 | const res = await http('post', '/api/w8t/faultCenter/faultCenterReset', params);
84 | message.open({
85 | type: 'success',
86 | content: '故障中心信息编辑成功',
87 | });
88 | return res;
89 | } catch (error) {
90 | message.open({
91 | type: 'error',
92 | content: `故障中心信息编辑失败`,
93 | });
94 | return error
95 | }
96 | }
97 |
98 | export {
99 | FaultCenterList,
100 | FaultCenterSearch,
101 | FaultCenterCreate,
102 | FaultCenterUpdate,
103 | FaultCenterDelete,
104 | FaultCenterReset
105 | }
--------------------------------------------------------------------------------
/src/api/kubernetes.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getKubernetesResourceList(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/kubernetes/getResourceList', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '获取Kubernetes资源列表失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function getKubernetesReasonList(params) {
18 | try {
19 | const res = await http('get', '/api/w8t/kubernetes/getReasonList', params);
20 | return res;
21 | } catch (error) {
22 | message.open({
23 | type: 'error',
24 | content: '获取Kubernetes事件类型列表失败',
25 | });
26 | return error
27 | }
28 | }
29 |
30 | export {
31 | getKubernetesResourceList,
32 | getKubernetesReasonList
33 | }
--------------------------------------------------------------------------------
/src/api/notice.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getNoticeList(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/notice/noticeList', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '通知对象列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function createNotice(params) {
18 | try {
19 | const res = await http('post', '/api/w8t/notice/noticeCreate', params);
20 | message.open({
21 | type: 'success',
22 | content: '通知对象创建成功',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '通知对象创建失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function updateNotice(params) {
35 | try {
36 | const res = await http('post', '/api/w8t/notice/noticeUpdate', params);
37 | message.open({
38 | type: 'success',
39 | content: '通知对象更新成功',
40 | });
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '通知对象更新失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | async function deleteNotice(params) {
52 | try {
53 | const res = await http('post', `/api/w8t/notice/noticeDelete`, params);
54 | message.open({
55 | type: 'success',
56 | content: '通知对象删除成功',
57 | });
58 | return res;
59 | } catch (error) {
60 | message.open({
61 | type: 'error',
62 | content: '通知对象删除失败',
63 | });
64 | return error
65 | }
66 | }
67 |
68 | async function searchNotice(params) {
69 | try {
70 | const res = await http('get', '/api/w8t/notice/noticeSearch', params);
71 | return res;
72 | } catch (error) {
73 | message.open({
74 | type: 'error',
75 | content: '搜索通知对象失败',
76 | });
77 | return error
78 | }
79 | }
80 |
81 | async function noticeRecordList(params) {
82 | try {
83 | const res = await http('get', '/api/w8t/notice/noticeRecordList',params);
84 | return res;
85 | } catch (error) {
86 | message.open({
87 | type: 'error',
88 | content: '获取通知记录列表失败',
89 | });
90 | return error
91 | }
92 | }
93 |
94 | async function noticeRecordMetric() {
95 | try {
96 | const res = await http('get', '/api/w8t/notice/noticeRecordMetric');
97 | return res;
98 | } catch (error) {
99 | message.open({
100 | type: 'error',
101 | content: '获取通知记录指标失败',
102 | });
103 | return error
104 | }
105 | }
106 |
107 | export {
108 | getNoticeList,
109 | createNotice,
110 | updateNotice,
111 | deleteNotice,
112 | searchNotice,
113 | noticeRecordList,
114 | noticeRecordMetric
115 | }
--------------------------------------------------------------------------------
/src/api/noticeTmpl.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getNoticeTmplList() {
5 | try {
6 | const res = await http('get', '/api/w8t/noticeTemplate/noticeTemplateList');
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '通知模版列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function createNoticeTmpl(params) {
18 | try {
19 | const res = await http('post', `/api/w8t/noticeTemplate/noticeTemplateCreate`, params);
20 | message.open({
21 | type: 'success',
22 | content: '通知模版创建成功',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '通知模版创建失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function updateNoticeTmpl(params) {
35 | try {
36 | const res = await http('post', `/api/w8t/noticeTemplate/noticeTemplateUpdate`, params);
37 | message.open({
38 | type: 'success',
39 | content: '通知模版更新成功',
40 | });
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '通知模版更新失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | async function deleteNoticeTmpl(params) {
52 | try {
53 | const res = await http('post', `/api/w8t/noticeTemplate/noticeTemplateDelete`,params);
54 | message.open({
55 | type: 'success',
56 | content: '通知模版删除成功',
57 | });
58 | return res;
59 | } catch (error) {
60 | message.open({
61 | type: 'error',
62 | content: '通知模版删除失败',
63 | });
64 | return error
65 | }
66 | }
67 |
68 | async function searchNoticeTmpl(params) {
69 | try {
70 | const res = await http('get', '/api/w8t/noticeTemplate/searchNoticeTmpl', params);
71 | return res;
72 | } catch (error) {
73 | message.open({
74 | type: 'error',
75 | content: '搜索通知模版失败',
76 | });
77 | return error
78 | }
79 | }
80 |
81 | export {
82 | getNoticeTmplList,
83 | createNoticeTmpl,
84 | updateNoticeTmpl,
85 | deleteNoticeTmpl,
86 | searchNoticeTmpl
87 | }
--------------------------------------------------------------------------------
/src/api/other.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getAllUsers(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/user/searchDutyUser', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '获取用户信息失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function getDashboardInfo(params) {
18 | try {
19 | const res = await http('get', '/api/system/getDashboardInfo', params);
20 | return res;
21 | } catch (error) {
22 | message.open({
23 | type: 'error',
24 | content: '获取仪表盘数据失败',
25 | });
26 | return error
27 | }
28 | }
29 |
30 | async function getJaegerService(params) {
31 | try {
32 | const queryString = Object.keys(params)
33 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
34 | .filter(Boolean)
35 | .join('&');
36 | const res = await http('get', `/api/w8t/c/getJaegerService?${queryString}`);
37 | return res;
38 | } catch (error) {
39 | message.open({
40 | type: 'error',
41 | content: '获取Jaeger服务列表失败',
42 | });
43 | return error
44 | }
45 | }
46 |
47 | async function queryPromMetrics(params) {
48 | try {
49 | const res = await http('get', `/api/w8t/datasource/promQuery`, params);
50 | return res;
51 | } catch (error) {
52 | message.open({
53 | type: 'error',
54 | content: '查询Metrics失败',
55 | });
56 | return error
57 | }
58 | }
59 |
60 | export {
61 | getAllUsers,
62 | getDashboardInfo,
63 | getJaegerService,
64 | queryPromMetrics
65 | }
--------------------------------------------------------------------------------
/src/api/permissions.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getPermissionsList() {
5 | try {
6 | const res = await http('get', `/api/w8t/permissions/permsList`);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '权限列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | export {
18 | getPermissionsList
19 | }
--------------------------------------------------------------------------------
/src/api/probing.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function ProbingList(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/probing/listProbing', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '拨测列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function ProbingSearch(params) {
18 | try {
19 | const res = await http('get', '/api/w8t/probing/searchProbing', params);
20 | return res;
21 | } catch (error) {
22 | message.open({
23 | type: 'error',
24 | content: '拨测规则信息获取失败',
25 | });
26 | return error
27 | }
28 | }
29 |
30 | async function ProbingCreate(params) {
31 | try {
32 | const res = await http('post', '/api/w8t/probing/createProbing', params);
33 | message.open({
34 | type: 'success',
35 | content: '拨测规则创建成功',
36 | });
37 | return res;
38 | } catch (error) {
39 | message.open({
40 | type: 'error',
41 | content: '拨测规则创建失败',
42 | });
43 | return error
44 | }
45 | }
46 |
47 | async function ProbingUpdate(params) {
48 | try {
49 | const res = await http('post', '/api/w8t/probing/updateProbing', params);
50 | message.open({
51 | type: 'success',
52 | content: '拨测规则更新成功',
53 | });
54 | return res;
55 | } catch (error) {
56 | message.open({
57 | type: 'error',
58 | content: '拨测规则更新失败',
59 | });
60 | return error
61 | }
62 | }
63 |
64 | async function ProbingDelete(params) {
65 | try {
66 | const res = await http('post', '/api/w8t/probing/deleteProbing', params);
67 | message.open({
68 | type: 'success',
69 | content: '拨测规则删除成功',
70 | });
71 | return res;
72 | } catch (error) {
73 | message.open({
74 | type: 'error',
75 | content: '拨测规则删除失败',
76 | });
77 | return error
78 | }
79 | }
80 |
81 | async function ProbingOnce(params) {
82 | try {
83 | const res = await http('post', '/api/w8t/probing/onceProbing', params);
84 | message.open({
85 | type: 'success',
86 | content: '拨测请求提交成功!',
87 | });
88 | return res;
89 | } catch (error) {
90 | message.open({
91 | type: 'error',
92 | content: '拨测请求提交失败!',
93 | });
94 | return error
95 | }
96 | }
97 |
98 | async function ProbingGetHistory(params) {
99 | return await http('get', '/api/w8t/probing/getProbingHistory', params);
100 | }
101 |
102 | export {
103 | ProbingList,
104 | ProbingSearch,
105 | ProbingCreate,
106 | ProbingUpdate,
107 | ProbingDelete,
108 | ProbingOnce,
109 | ProbingGetHistory
110 | }
--------------------------------------------------------------------------------
/src/api/role.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getRoleList() {
5 | try {
6 | const res = await http('get', `/api/w8t/role/roleList`);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '角色列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function createRole(params) {
18 | try {
19 | const res = await http('post', `/api/w8t/role/roleCreate`, params);
20 | message.open({
21 | type: 'success',
22 | content: '角色创建成功',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '角色创建失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function updateRole(params) {
35 | try {
36 | const res = await http('post', `/api/w8t/role/roleUpdate`, params);
37 | message.open({
38 | type: 'success',
39 | content: '角色更新成功',
40 | });
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '角色更新失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | async function deleteRole(params) {
52 | try {
53 | const res = await http('post', `/api/w8t/role/roleDelete`,params);
54 | message.open({
55 | type: 'success',
56 | content: '角色删除成功',
57 | });
58 | return res;
59 | } catch (error) {
60 | message.open({
61 | type: 'error',
62 | content: '角色删除失败',
63 | });
64 | return error
65 | }
66 | }
67 |
68 | export {
69 | getRoleList,
70 | createRole,
71 | updateRole,
72 | deleteRole
73 | }
--------------------------------------------------------------------------------
/src/api/rule.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getRuleList(params) {
5 | try {
6 | const res = await http('get', `/api/w8t/rule/ruleList`, params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '规则获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function createRule(params) {
18 | try {
19 | const res = await http('post', '/api/w8t/rule/ruleCreate', params);
20 | message.open({
21 | type: 'success',
22 | content: '创建规则成功',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '创建规则失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function updateRule(params) {
35 | try {
36 | const res = await http('post', '/api/w8t/rule/ruleUpdate', params);
37 | message.open({
38 | type: 'success',
39 | content: '规则更新成功',
40 | });
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '规则更新失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | async function deleteRule(params) {
52 | try {
53 | const res = await http('post', `/api/w8t/rule/ruleDelete`, params);
54 | message.open({
55 | type: 'success',
56 | content: '规则删除成功',
57 | });
58 | return res;
59 | } catch (error) {
60 | message.open({
61 | type: 'error',
62 | content: '规则删除失败',
63 | });
64 | return error
65 | }
66 | }
67 |
68 | async function searchRuleInfo(params) {
69 | try {
70 | const res = await http('get', `/api/w8t/rule/ruleSearch`, params);
71 | return res;
72 | } catch (error) {
73 | message.open({
74 | type: 'error',
75 | content: '规则信息查询失败',
76 | });
77 | return error
78 | }
79 | }
80 |
81 | async function getRuleGroupList(params) {
82 | try {
83 | const headers = {
84 | 'TenantID': 'xxxxxxxxx'
85 | }
86 | const res = await http('get', '/api/w8t/ruleGroup/ruleGroupList', params, headers);
87 | return res;
88 | } catch (error) {
89 | message.open({
90 | type: 'error',
91 | content: '规则组列表获取失败',
92 | });
93 | return error
94 | }
95 | }
96 |
97 | async function createRuleGroup(params) {
98 | try {
99 | const res = await http('post', '/api/w8t/ruleGroup/ruleGroupCreate', params);
100 | message.open({
101 | type: 'success',
102 | content: '规则组创建成功',
103 | });
104 | return res;
105 | } catch (error) {
106 | message.open({
107 | type: 'error',
108 | content: '创建规则组失败',
109 | });
110 | return error
111 | }
112 | }
113 |
114 | async function updateRuleGroup(params) {
115 | try {
116 | const res = await http('post', '/api/w8t/ruleGroup/ruleGroupUpdate', params);
117 | message.open({
118 | type: 'success',
119 | content: '规则组更新成功',
120 | });
121 | return res;
122 | } catch (error) {
123 | message.open({
124 | type: 'error',
125 | content: '规则组更新失败',
126 | });
127 | return error
128 | }
129 | }
130 |
131 | async function deleteRuleGroup(params) {
132 | try {
133 | const res = await http('post', `/api/w8t/ruleGroup/ruleGroupDelete`,params);
134 | message.open({
135 | type: 'success',
136 | content: '规则组删除成功',
137 | });
138 | return res;
139 | } catch (error) {
140 | message.open({
141 | type: 'error',
142 | content: '规则组删除失败',
143 | });
144 | return error
145 | }
146 | }
147 |
148 | export {
149 | getRuleList,
150 | createRule,
151 | updateRule,
152 | deleteRule,
153 | searchRuleInfo,
154 | getRuleGroupList,
155 | createRuleGroup,
156 | updateRuleGroup,
157 | deleteRuleGroup,
158 | }
--------------------------------------------------------------------------------
/src/api/ruleTmpl.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getRuleTmplList(params) {
5 | try {
6 | const queryString = Object.keys(params)
7 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
8 | .filter(Boolean)
9 | .join('&');
10 | const res = await http('get', `/api/w8t/ruleTmpl/ruleTmplList?${queryString}`);
11 | return res;
12 | } catch (error) {
13 | message.open({
14 | type: 'error',
15 | content: '规则模版列表获取失败',
16 | });
17 | return error
18 | }
19 | }
20 |
21 | async function createRuleTmpl(params) {
22 | try {
23 | const res = await http('post', `/api/w8t/ruleTmpl/ruleTmplCreate`, params);
24 | message.open({
25 | type: 'success',
26 | content: '规则模版创建成功',
27 | });
28 | return res;
29 | } catch (error) {
30 | message.open({
31 | type: 'error',
32 | content: '规则模版创建失败',
33 | });
34 | return error
35 | }
36 | }
37 |
38 | async function updateRuleTmpl(params) {
39 | try {
40 | const res = await http('post', `/api/w8t/ruleTmpl/ruleTmplUpdate`, params);
41 | message.open({
42 | type: 'success',
43 | content: '规则模版更新成功',
44 | });
45 | return res;
46 | } catch (error) {
47 | message.open({
48 | type: 'error',
49 | content: '规则模版更新失败',
50 | });
51 | return error
52 | }
53 | }
54 |
55 | async function deleteRuleTmpl(params) {
56 | try {
57 |
58 | const res = await http('post', `/api/w8t/ruleTmpl/ruleTmplDelete`, params);
59 | message.open({
60 | type: 'success',
61 | content: '规则模版删除成功',
62 | });
63 | return res;
64 | } catch (error) {
65 | message.open({
66 | type: 'error',
67 | content: '规则模版删除失败',
68 | });
69 | return error
70 | }
71 | }
72 |
73 | async function getRuleTmplGroupList(params) {
74 | try {
75 | const res = await http('get', `/api/w8t/ruleTmplGroup/ruleTmplGroupList`, params);
76 | return res;
77 | } catch (error) {
78 | message.open({
79 | type: 'error',
80 | content: '规则模版组获取失败',
81 | });
82 | return error
83 | }
84 | }
85 |
86 | async function createRuleTmplGroup(params) {
87 | try {
88 | const res = await http('post', `/api/w8t/ruleTmplGroup/ruleTmplGroupCreate`, params);
89 | message.open({
90 | type: 'success',
91 | content: '规则模版组创建成功',
92 | });
93 | return res;
94 | } catch (error) {
95 | message.open({
96 | type: 'error',
97 | content: '规则模版组创建失败',
98 | });
99 | return error
100 | }
101 | }
102 |
103 | async function updateRuleTmplGroup(params) {
104 | try {
105 | const res = await http('post', `/api/w8t/ruleTmplGroup/ruleTmplGroupUpdate`, params);
106 | message.open({
107 | type: 'success',
108 | content: '规则模版组更新成功',
109 | });
110 | return res;
111 | } catch (error) {
112 | message.open({
113 | type: 'error',
114 | content: '规则模版组更新失败',
115 | });
116 | return error
117 | }
118 | }
119 |
120 | async function deleteRuleTmplGroup(params) {
121 | try {
122 | const res = await http('post', `/api/w8t/ruleTmplGroup/ruleTmplGroupDelete`,params);
123 | message.open({
124 | type: 'success',
125 | content: '规则模版组删除成功',
126 | });
127 | return res;
128 | } catch (error) {
129 | message.open({
130 | type: 'error',
131 | content: '规则模版组删除失败',
132 | });
133 | return error
134 | }
135 | }
136 |
137 | export {
138 | getRuleTmplList,
139 | createRuleTmpl,
140 | updateRuleTmpl,
141 | deleteRuleTmpl,
142 | getRuleTmplGroupList,
143 | createRuleTmplGroup,
144 | updateRuleTmplGroup,
145 | deleteRuleTmplGroup
146 | }
--------------------------------------------------------------------------------
/src/api/settings.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getSystemSetting() {
5 | try {
6 | const res = await http('get', '/api/w8t/setting/getSystemSetting');
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '系统配置获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function saveSystemSetting(params) {
18 | try {
19 | const res = await http('post', '/api/w8t/setting/saveSystemSetting', params);
20 | message.open({
21 | type: 'success',
22 | content: '系统配置保存成功, 且立即生效!',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '系统配置保存失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | export {
35 | getSystemSetting,
36 | saveSystemSetting
37 | }
--------------------------------------------------------------------------------
/src/api/silence.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getSilenceList(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/silence/silenceList', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '静默规则列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function createSilence(params) {
18 | try {
19 | const res = await http('post', '/api/w8t/silence/silenceCreate', params);
20 | message.open({
21 | type: 'success',
22 | content: '静默规则创建成功',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: '静默规则创建失败',
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function updateSilence(params) {
35 | try {
36 | const res = await http('post', '/api/w8t/silence/silenceUpdate', params);
37 | message.open({
38 | type: 'success',
39 | content: '静默规则更新成功',
40 | });
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '静默规则更新失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | async function deleteSilence(params) {
52 | try {
53 | const res = await http('post', `/api/w8t/silence/silenceDelete`, params);
54 | message.open({
55 | type: 'success',
56 | content: '静默规则删除成功',
57 | });
58 | return res;
59 | } catch (error) {
60 | message.open({
61 | type: 'error',
62 | content: '静默规则删除失败',
63 | });
64 | return error
65 | }
66 | }
67 |
68 | export {
69 | getSilenceList,
70 | createSilence,
71 | updateSilence,
72 | deleteSilence
73 | }
--------------------------------------------------------------------------------
/src/api/subscribe.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function listSubscribe(params) {
5 | try {
6 | const res = await http('get', '/api/w8t/subscribe/listSubscribe', params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '订阅列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function createSubscribe(params) {
18 | try {
19 | const res = await http('post', '/api/w8t/subscribe/createSubscribe', params);
20 | message.open({
21 | type: 'success',
22 | content: '订阅规则创建成功',
23 | });
24 | return res;
25 | } catch (error) {
26 | message.open({
27 | type: 'error',
28 | content: `订阅规则创建失败`,
29 | });
30 | return error
31 | }
32 | }
33 |
34 | async function deleteSubscribe(params) {
35 | try {
36 | const res = await http('post', `/api/w8t/subscribe/deleteSubscribe`, params);
37 | message.open({
38 | type: 'success',
39 | content: '订阅规则删除成功',
40 | });
41 | return res;
42 | } catch (error) {
43 | message.open({
44 | type: 'error',
45 | content: '订阅规则删除失败',
46 | });
47 | return error
48 | }
49 | }
50 |
51 | export {
52 | listSubscribe,
53 | createSubscribe,
54 | deleteSubscribe
55 | }
--------------------------------------------------------------------------------
/src/api/tenant.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getTenantList(params) {
5 | try {
6 | const res = await http('get', `/api/w8t/tenant/getTenantList`,params);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '租户列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 | async function getTenant(params) {
18 | try {
19 | const res = await http('get', `/api/w8t/tenant/getTenant`, params);
20 | return res;
21 | } catch (error) {
22 | message.open({
23 | type: 'error',
24 | content: '租户信息获取失败',
25 | });
26 | return error
27 | }
28 | }
29 |
30 | async function createTenant(params) {
31 | try {
32 | const res = await http('post', `/api/w8t/tenant/createTenant`, params);
33 | message.open({
34 | type: 'success',
35 | content: '租户创建成功',
36 | });
37 | return res;
38 | } catch (error) {
39 | message.open({
40 | type: 'error',
41 | content: '租户创建失败',
42 | });
43 | return error
44 | }
45 | }
46 |
47 | async function updateTenant(params) {
48 | try {
49 | const res = await http('post', `/api/w8t/tenant/updateTenant`, params);
50 | message.open({
51 | type: 'success',
52 | content: '租户更新成功',
53 | });
54 | return res;
55 | } catch (error) {
56 | message.open({
57 | type: 'error',
58 | content: '租户更新失败',
59 | });
60 | return error
61 | }
62 | }
63 |
64 | async function deleteTenant(params) {
65 | try {
66 | const res = await http('post', `/api/w8t/tenant/deleteTenant`, params);
67 | message.open({
68 | type: 'success',
69 | content: '租户删除成功',
70 | });
71 | return res;
72 | } catch (error) {
73 | message.open({
74 | type: 'error',
75 | content: '租户删除失败',
76 | });
77 | return error
78 | }
79 | }
80 |
81 | async function getUsersForTenant(params) {
82 | try {
83 | const res = await http('get', `/api/w8t/tenant/getUsersForTenant`, params);
84 | return res;
85 | } catch (error) {
86 | message.open({
87 | type: 'error',
88 | content: '租户成员列表获取失败',
89 | });
90 | return error
91 | }
92 | }
93 |
94 | async function addUsersToTenant(params) {
95 | try {
96 | const res = await http('post', `/api/w8t/tenant/addUsersToTenant`, params);
97 | return res;
98 | } catch (error) {
99 | message.open({
100 | type: 'error',
101 | content: '向租户添加成员失败',
102 | });
103 | return error
104 | }
105 | }
106 |
107 | async function delUsersOfTenant(params) {
108 | try {
109 | const res = await http('post', `/api/w8t/tenant/delUsersOfTenant`, params);
110 | return res;
111 | } catch (error) {
112 | message.open({
113 | type: 'error',
114 | content: '删除租户成员失败',
115 | });
116 | return error
117 | }
118 | }
119 |
120 | async function changeTenantUserRole(params) {
121 | try {
122 | const res = await http('post', `/api/w8t/tenant/changeTenantUserRole`, params);
123 | return res;
124 | } catch (error) {
125 | message.open({
126 | type: 'error',
127 | content: '修改租户成员角色失败',
128 | });
129 | return error
130 | }
131 | }
132 |
133 | export {
134 | getTenantList,
135 | createTenant,
136 | updateTenant,
137 | deleteTenant,
138 | getTenant,
139 | getUsersForTenant,
140 | addUsersToTenant,
141 | delUsersOfTenant,
142 | changeTenantUserRole
143 | }
--------------------------------------------------------------------------------
/src/api/user.jsx:
--------------------------------------------------------------------------------
1 | import http from '../utils/http';
2 | import { message } from 'antd';
3 |
4 | async function getUserList() {
5 | try {
6 | const res = await http('get', `/api/w8t/user/userList`);
7 | return res;
8 | } catch (error) {
9 | message.open({
10 | type: 'error',
11 | content: '用户列表获取失败',
12 | });
13 | return error
14 | }
15 | }
16 |
17 |
18 | async function loginUser(params) {
19 | try {
20 | const res = await http('post', `/api/system/login`, params);
21 | return res;
22 | } catch (error) {
23 | message.open({
24 | type: 'error',
25 | content: '用户不存在或密码错误',
26 | });
27 | return error
28 | }
29 | }
30 |
31 | async function registerUser(params) {
32 | try {
33 | const res = await http('post', `/api/system/register`, params);
34 | message.open({
35 | type: 'success',
36 | content: '用户注册成功',
37 | });
38 | return res;
39 | } catch (error) {
40 | message.open({
41 | type: 'error',
42 | content: '用户注册失败',
43 | });
44 | return error
45 | }
46 | }
47 |
48 | async function updateUser(params) {
49 | try {
50 | const res = await http('post', `/api/w8t/user/userUpdate`, params);
51 | message.open({
52 | type: 'success',
53 | content: '用户更新成功',
54 | });
55 | return res;
56 | } catch (error) {
57 | message.open({
58 | type: 'error',
59 | content: '用户更新失败',
60 | });
61 | return error
62 | }
63 | }
64 |
65 | async function deleteUser(params) {
66 | try {
67 | const res = await http('post', `/api/w8t/user/userDelete`,params);
68 | message.open({
69 | type: 'success',
70 | content: '用户删除成功',
71 | });
72 | return res;
73 | } catch (error) {
74 | message.open({
75 | type: 'error',
76 | content: '用户删除失败',
77 | });
78 | return error
79 | }
80 | }
81 |
82 | async function getUserInfo() {
83 | try {
84 | const res = await http('get', `/api/system/userInfo`);
85 | return res;
86 | } catch (error) {
87 | message.open({
88 | type: 'error',
89 | content: '用户信息获取失败',
90 | });
91 | return error
92 | }
93 | }
94 |
95 | async function checkUser(params) {
96 | try {
97 | const queryString = Object.keys(params)
98 | .map(key => params[key] !== undefined ? `${key}=${params[key]}` : '')
99 | .filter(Boolean)
100 | .join('&');
101 | const res = await http('get', `/api/system/checkUser?${queryString}`);
102 | return res;
103 | } catch (error) {
104 | return error
105 | }
106 | }
107 |
108 | async function changeUserPass(params) {
109 | try {
110 | const res = await http('post', `/api/w8t/user/userChangePass`, params);
111 | message.open({
112 | type: 'success',
113 | content: '修改密码成功',
114 | });
115 | return res;
116 | } catch (error) {
117 | message.open({
118 | type: 'error',
119 | content: '修改密码失败',
120 | });
121 | return error
122 | }
123 | }
124 |
125 | async function searchUser(params) {
126 | try {
127 | const res = await http('get', '/api/w8t/user/searchUser', params);
128 | return res;
129 | } catch (error) {
130 | message.open({
131 | type: 'error',
132 | content: '获取用户信息失败',
133 | });
134 | return error
135 | }
136 | }
137 |
138 | export {
139 | getUserList,
140 | loginUser,
141 | registerUser,
142 | updateUser,
143 | deleteUser,
144 | checkUser,
145 | getUserInfo,
146 | changeUserPass,
147 | searchUser
148 | }
--------------------------------------------------------------------------------
/src/components/context/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState, useContext } from 'react';
2 |
3 | // 创建一个 Context
4 | const DataContext = createContext();
5 |
6 | // 创建一个提供者组件
7 | export const DataProvider = ({ children }) => {
8 | const [data, setData] = useState('');
9 |
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | };
16 |
17 | // 自定义 hook,方便使用 Context
18 | export const useData = () => {
19 | return useContext(DataContext);
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/index.css:
--------------------------------------------------------------------------------
1 | .ant-select-selector {
2 | border: none;
3 | }
4 |
5 | .ant-card:active {
6 | transform: scale(0.98); /* 轻微缩小卡片 */
7 | }
8 |
9 | .ant-menu-light.ant-menu-root.ant-menu-inline {
10 | border-inline-end: none;
11 | }
--------------------------------------------------------------------------------
/src/context/RuleContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useState } from 'react';
2 |
3 | const RuleContext = createContext();
4 |
5 | export const ContextProvider = ({ children }) => {
6 | const [ruleTemplate, setRuleTemplate] = useState(null);
7 |
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | };
14 |
15 | export const useRule = () => useContext(RuleContext);
--------------------------------------------------------------------------------
/src/img/WatchAlert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opsre/WatchAlert-web/85e8f39a1d4c2c8884eaee4bd2257b357a0f5716/src/img/WatchAlert.png
--------------------------------------------------------------------------------
/src/img/github_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opsre/WatchAlert-web/85e8f39a1d4c2c8884eaee4bd2257b357a0f5716/src/img/github_logo.png
--------------------------------------------------------------------------------
/src/img/logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opsre/WatchAlert-web/85e8f39a1d4c2c8884eaee4bd2257b357a0f5716/src/img/logo.jpeg
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root{
2 | height: 100vh;
3 | width: 100%;
4 | }
5 | #root {
6 | height: 100vh;
7 | width: 100%;
8 | }
9 |
10 | body {
11 | margin: 0;
12 | padding: 0;
13 | height: 100vh;
14 | width: 100%;
15 | background-color: rgb(238, 240, 243);
16 | }
17 |
18 | *::-webkit-scrollbar {
19 | display: none;
20 | }
21 |
22 | * {
23 | scrollbar-width: none;
24 | }
25 |
26 | * {
27 | -ms-overflow-style: none;
28 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import App from './App.jsx';
5 | import { ConfigProvider } from 'antd';
6 | import zhCN from 'antd/es/locale/zh_CN.js';
7 |
8 | ReactDOM.createRoot(document.getElementById('root')).render(
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/src/pages/alert/assets/event.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/assets/log.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/assets/metric.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/assets/trace.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/preview/searchViewLogs.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Spin, Tag, Divider, Empty } from "antd";
3 | import {SearchViewLogsContent} from "../../../api/datasource";
4 |
5 | export const SearchViewLogs = ({ type, datasourceId, index, query }) => {
6 | const [logs, setLogs] = useState([]);
7 | const [loading, setLoading] = useState(false);
8 | const [error, setError] = useState(null);
9 |
10 | useEffect(() => {
11 | const fetchLogs = async () => {
12 | try {
13 | setLoading(true);
14 | setError(null);
15 | const { code, data, msg } = await SearchViewLogsContent({
16 | type,
17 | datasourceId,
18 | index,
19 | query
20 | });
21 |
22 | if (code === 200) {
23 | setLogs(data || []);
24 | } else {
25 | setError(msg || 'Failed to load logs');
26 | }
27 | } catch (err) {
28 | setError('Network error occurred');
29 | console.error('Fetch error:', err);
30 | } finally {
31 | setLoading(false);
32 | }
33 | };
34 |
35 | fetchLogs();
36 | }, []);
37 |
38 | const renderLogMessage = (message) => {
39 | try {
40 | // 如果 message 是对象,就格式化显示 JSON
41 | const prettyJson = JSON.stringify(message, null, 2);
42 |
43 | return (
44 |
54 | {prettyJson}
55 |
56 | );
57 | } catch (e) {
58 | // 如果不是对象或无法转换为 JSON,则显示原始文本
59 | return (
60 |
67 |
Invalid Format
68 |
69 | {String(message)}
70 |
71 |
72 | );
73 | }
74 | };
75 |
76 | const renderLogItem = (item, index) => (
77 | <>
78 | {item.ProviderName}
79 | {item.Message?.length || 0} messages
80 |
81 |
82 |
83 |
Metadata
84 |
85 | {Object.entries(item.Metric || {}).map(([key, value]) => (
86 |
87 | {key}: {value}
88 |
89 | ))}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
Log Messages
97 | {item.Message?.length > 0 ? (
98 |
99 | {item.Message.map((msg, i) => (
100 |
{renderLogMessage(msg)}
101 | ))}
102 |
103 | ) : (
104 |
105 | )}
106 |
107 | >
108 | );
109 |
110 | return (
111 |
112 | {loading ? (
113 |
119 |
120 |
121 | ) : error ? (
122 |
128 |
129 |
130 | ) : logs.length === 0 ? (
131 |
137 |
138 |
139 | ) : (
140 |
144 | {logs.map(renderLogItem)}
145 |
146 | )}
147 |
148 | );
149 | };
--------------------------------------------------------------------------------
/src/pages/alert/rule/img/AWSlogo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/rule/img/ElasticSearch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/rule/img/Kubernetes.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/rule/img/Prometheus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/rule/img/alicloud.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/alert/rule/index.css:
--------------------------------------------------------------------------------
1 | .rule-config-container {
2 | border: 1px solid #ccc;
3 | padding: 16px;
4 | border-radius: 4px;
5 | margin-top: 10px;
6 | }
7 |
8 | .log-rule-config-container {
9 | border: 1px solid #ccc;
10 | padding: 16px;
11 | border-radius: 4px;
12 | margin-top: 10px;
13 | margin-bottom: 20px;
14 | }
15 |
16 | .rule-item {
17 | display: flex;
18 | margin-bottom: 10px;
19 | }
20 |
21 | .rule-content {
22 | display: flex;
23 | }
24 |
25 | .duration-input {
26 | display: flex;
27 | margin-top: -20px;
28 | align-items: center;
29 | }
30 |
31 | .action-buttons {
32 | display: flex;
33 | align-items: center;
34 | justify-content: flex-end;
35 | margin-right: -15px;
36 | margin-top: -10px;
37 | }
38 |
39 |
40 | .scroll-container {
41 | max-height: 460px; /* 根据需要调整 */
42 | overflow-y: auto;
43 | overflow-x: auto; /* 添加横向滚动 */
44 | border: 1px solid #ddd;
45 | /*padding: 8px; !* 增加内部边距 *!*/
46 | /*white-space: nowrap; !* 防止内容自动换行 *!*/
47 | }
48 |
49 | .list-item-content {
50 | display: flex;
51 | justify-content: space-between;
52 | width: 100%;
53 | }
54 |
55 | .value {
56 | margin-left: auto;
57 | padding-left: 20px; /* 增加间距以使内容更易读 */
58 | }
59 |
60 | /* 状态容器 */
61 | .status-container {
62 | display: flex;
63 | align-items: center;
64 | gap: 8px;
65 | }
66 |
67 | /* 小圆点 */
68 | .status-dot {
69 | width: 8px;
70 | height: 8px;
71 | border-radius: 50%;
72 | position: relative;
73 | }
74 |
75 | /* 启用状态的小圆点 */
76 | .status-enabled {
77 | background-color: #52c41a; /* 绿色 */
78 | animation: pulse-enabled 1.5s infinite;
79 | }
80 |
81 | /* 禁用状态的小圆点 */
82 | .status-disabled {
83 | background-color: #ff4d4f; /* 红色 */
84 | animation: pulse-disabled 1.5s infinite;
85 | }
86 |
87 | /* 启用状态动态效果 */
88 | @keyframes pulse-enabled {
89 | 0% {
90 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.4);
91 | }
92 | 70% {
93 | box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
94 | }
95 | 100% {
96 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
97 | }
98 | }
99 |
100 | /* 禁用状态动态效果 */
101 | @keyframes pulse-disabled {
102 | 0% {
103 | box-shadow: 0 0 0 0 rgba(196, 26, 26, 0.4);
104 | }
105 | 70% {
106 | box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
107 | }
108 | 100% {
109 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
110 | }
111 | }
--------------------------------------------------------------------------------
/src/pages/alert/ruleGroup/AlertRuleGroupCreateModal.jsx:
--------------------------------------------------------------------------------
1 | import { createRuleGroup, updateRuleGroup } from '../../../api/rule'
2 | import { Modal, Form, Input, Button, message } from 'antd'
3 | import React, { useState, useEffect } from 'react'
4 |
5 | const MyFormItemContext = React.createContext([])
6 |
7 | function toArr(str) {
8 | return Array.isArray(str) ? str : [str]
9 | }
10 |
11 | const MyFormItem = ({ name, ...props }) => {
12 | const prefixPath = React.useContext(MyFormItemContext)
13 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
14 | return
15 | }
16 |
17 | export const AlertRuleGroupCreateModal = ({ visible, onClose, selectedRow, type, handleList, pagination}) => {
18 | const [form] = Form.useForm()
19 |
20 | // 禁止输入空格
21 | const [spaceValue, setSpaceValue] = useState('')
22 |
23 | const handleInputChange = (e) => {
24 | // 移除输入值中的空格
25 | const newValue = e.target.value.replace(/\s/g, '')
26 | setSpaceValue(newValue)
27 | }
28 |
29 | const handleKeyPress = (e) => {
30 | // 阻止空格键的默认行为
31 | if (e.key === ' ') {
32 | e.preventDefault()
33 | }
34 | }
35 |
36 | useEffect(() => {
37 | if (selectedRow) {
38 | form.setFieldsValue({
39 | id: selectedRow.id,
40 | name: selectedRow.name,
41 | description: selectedRow.description,
42 | })
43 | }
44 |
45 | }, [selectedRow, form])
46 |
47 | const handleCreate = async (data) => {
48 | try {
49 | await createRuleGroup(data)
50 | handleList(pagination.index,pagination.size)
51 | } catch (error) {
52 | console.error(error)
53 | }
54 | }
55 |
56 | const handleUpdate = async (data) => {
57 | try {
58 | await updateRuleGroup(data)
59 | handleList(pagination.index,pagination.size)
60 | } catch (error) {
61 | console.error(error)
62 | }
63 | }
64 |
65 | const handleFormSubmit = async (values) => {
66 |
67 | if (type === 'create') {
68 | await handleCreate(values)
69 | }
70 |
71 | if (type === 'update') {
72 | const newValues = {
73 | ...values,
74 | tenantId: selectedRow.tenantId,
75 | id: selectedRow.id
76 | }
77 | await handleUpdate(newValues)
78 | }
79 |
80 | onClose()
81 | }
82 |
83 | return (
84 |
85 |
115 |
116 | )
117 | }
--------------------------------------------------------------------------------
/src/pages/alert/tmplGroup/RuleTemplateGroupCreateModal.jsx:
--------------------------------------------------------------------------------
1 | import { Modal, Form, Input, Button } from 'antd'
2 | import React, { useState, useEffect } from 'react'
3 | import {createRuleTmplGroup, updateRuleTmplGroup} from '../../../api/ruleTmpl'
4 |
5 | const MyFormItemContext = React.createContext([])
6 |
7 | function toArr(str) {
8 | return Array.isArray(str) ? str : [str]
9 | }
10 |
11 | const MyFormItem = ({ name, ...props }) => {
12 | const prefixPath = React.useContext(MyFormItemContext)
13 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
14 | return
15 | }
16 |
17 | const RuleTemplateGroupCreateModal = ({ visible, onClose, selectedRow, openType, tmplType, handleList }) => {
18 | const [form] = Form.useForm()
19 |
20 | // 禁止输入空格
21 | const [spaceValue, setSpaceValue] = useState('')
22 |
23 | const handleInputChange = (e) => {
24 | // 移除输入值中的空格
25 | const newValue = e.target.value.replace(/\s/g, '')
26 | setSpaceValue(newValue)
27 | }
28 |
29 | const handleKeyPress = (e) => {
30 | // 阻止空格键的默认行为
31 | if (e.key === ' ') {
32 | e.preventDefault()
33 | }
34 | }
35 |
36 | useEffect(() => {
37 | if (selectedRow) {
38 | form.setFieldsValue({
39 | id: selectedRow.id,
40 | name: selectedRow.name,
41 | type: selectedRow.type,
42 | description: selectedRow.description,
43 | })
44 | }
45 |
46 | }, [selectedRow, form])
47 |
48 | const handleCreate = async (data) => {
49 | try {
50 | const params = {
51 | ...data,
52 | type: tmplType,
53 | }
54 | await createRuleTmplGroup(params)
55 | handleList()
56 | } catch (error) {
57 | console.error(error)
58 | }
59 | }
60 |
61 | const handleUpdate = async (data) => {
62 | try {
63 | const params = {
64 | ...data,
65 | id: selectedRow.id,
66 | type: selectedRow.type,
67 | }
68 | await updateRuleTmplGroup(params)
69 | handleList()
70 | } catch (error) {
71 | console.error(error)
72 | }
73 | }
74 |
75 | const handleFormSubmit = async (values) => {
76 | if (openType === 'create') {
77 | await handleCreate(values)
78 | }
79 |
80 | if (openType === 'update') {
81 | await handleUpdate(values)
82 | }
83 |
84 | // 关闭弹窗
85 | onClose()
86 | }
87 |
88 | return (
89 |
90 |
116 |
117 | )
118 | }
119 |
120 | export default RuleTemplateGroupCreateModal
--------------------------------------------------------------------------------
/src/pages/chart/noticeMetricChart.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | Area,
6 | AreaChart,
7 | CartesianGrid,
8 | XAxis,
9 | YAxis,
10 | Tooltip,
11 | Legend,
12 | ResponsiveContainer,
13 | } from "recharts"
14 |
15 | import {
16 | Typography,
17 | } from "antd"
18 |
19 | const { Text } = Typography
20 |
21 | // 告警等级颜色配置
22 | const SEVERITY_COLORS = {
23 | P0: '#ff4d4f',
24 | P1: '#faad14',
25 | P2: '#b0e1fb'
26 | }
27 |
28 | export const NoticeMetricChart = ({ data }) => {
29 | if (!data || !data.date || !data.series) {
30 | return (
31 |
32 | 暂无图表数据
33 |
34 | )
35 | }
36 |
37 | // 将接口返回的数据转换成 recharts 支持的格式
38 | const chartData = data.date.map((date, index) => ({
39 | name: date,
40 | P0: data.series.p0?.[index] ?? 0,
41 | P1: data.series.p1?.[index] ?? 0,
42 | P2: data.series.p2?.[index] ?? 0,
43 | }))
44 |
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
67 | {
70 | const date = new Date(value)
71 | return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
72 | }}
73 | axisLine={true}
74 | tickLine={false}
75 | tickMargin={8}
76 | />
77 |
82 |
83 | {
94 | const date = new Date(label)
95 | return 日期: {date.getFullYear()}-{date.getMonth() + 1}-{date.getDate()}
96 | }}
97 | formatter={(value, name) => [value, name]}
98 | />
99 |
100 |
101 |
102 |
110 |
118 |
126 |
127 |
128 | )
129 | }
--------------------------------------------------------------------------------
/src/pages/dashboards/dashboard/iframe.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { getDashboardFullUrl, getFolderInfo} from '../../../api/dashboard';
3 | import { useParams } from 'react-router-dom'
4 |
5 | export const GrafanaDashboardComponent = () => {
6 | const { fid,did } = useParams()
7 | const [iframeSrc, setIframeSrc] = useState('')
8 |
9 | useEffect(() => {
10 | run();
11 | }, []);
12 |
13 | const run = async () => {
14 | try {
15 | const fParams = {
16 | id: fid
17 | }
18 | const resInfo = await getFolderInfo(fParams)
19 | const params = {
20 | theme: resInfo.data.theme,
21 | grafanaHost: resInfo.data.grafanaHost,
22 | grafanaDashboardUid: did
23 | }
24 | const res = await getDashboardFullUrl(params)
25 | setIframeSrc(res.data)
26 | } catch (error) {
27 | console.error(error)
28 | }
29 | }
30 |
31 | return (
32 | <>
33 | Loading...
34 |
35 |
45 |
46 | >
47 | );
48 | };
--------------------------------------------------------------------------------
/src/pages/dashboards/dashboard/index.jsx:
--------------------------------------------------------------------------------
1 | import { Table, Input } from 'antd'
2 | import React, { useEffect, useState } from 'react'
3 | import { Link } from 'react-router-dom'
4 | import {
5 | getFolderInfo,
6 | getGrafanaDashboardList,
7 | } from '../../../api/dashboard';
8 | import { useParams } from 'react-router-dom'
9 |
10 | export const Dashboards = () => {
11 | const [list, setList] = useState()
12 | const { id } = useParams()
13 | const columns = [
14 | {
15 | title: '名称',
16 | dataIndex: 'title',
17 | key: 'title',
18 | render: (text, record) => (
19 |
20 |
21 |
26 | {text}
27 |
28 |
29 |
30 | ),
31 | },
32 | {
33 | title: 'ID',
34 | dataIndex: 'uid',
35 | key: 'uid',
36 | },
37 | ]
38 |
39 | useEffect(() => {
40 | handleList()
41 | }, [])
42 |
43 | const handleList = async () => {
44 | try {
45 | const fParams = {
46 | id: id
47 | }
48 | const resInfo = await getFolderInfo(fParams)
49 | const params = {
50 | grafanaHost: resInfo.data.grafanaHost,
51 | grafanaFolderId: resInfo.data.grafanaFolderId,
52 | grafanaVersion: resInfo.data.grafanaVersion,
53 | limit: 1000,
54 | }
55 | const res = await getGrafanaDashboardList(params)
56 | const d = res.data.map((item, index) => {
57 | return {
58 | key: index,
59 | ...item,
60 | }
61 | })
62 | setList(d)
63 | } catch (error) {
64 | console.error(error)
65 | }
66 | }
67 |
68 | return (
69 | <>
70 |
80 | >
81 | );
82 | };
--------------------------------------------------------------------------------
/src/pages/datasources/index.css:
--------------------------------------------------------------------------------
1 |
2 | /* 状态容器 */
3 | .status-container {
4 | display: flex;
5 | align-items: center;
6 | gap: 8px;
7 | }
8 |
9 | /* 小圆点 */
10 | .status-dot {
11 | width: 8px;
12 | height: 8px;
13 | border-radius: 50%;
14 | position: relative;
15 | }
16 |
17 | /* 启用状态的小圆点 */
18 | .status-enabled {
19 | background-color: #52c41a; /* 绿色 */
20 | animation: pulse-enabled 1.5s infinite;
21 | }
22 |
23 | /* 禁用状态的小圆点 */
24 | .status-disabled {
25 | background-color: #ff4d4f; /* 红色 */
26 | animation: pulse-disabled 1.5s infinite;
27 | }
28 |
29 | /* 启用状态动态效果 */
30 | @keyframes pulse-enabled {
31 | 0% {
32 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.4);
33 | }
34 | 70% {
35 | box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
36 | }
37 | 100% {
38 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
39 | }
40 | }
41 |
42 | /* 禁用状态动态效果 */
43 | @keyframes pulse-disabled {
44 | 0% {
45 | box-shadow: 0 0 0 0 rgba(196, 26, 26, 0.4);
46 | }
47 | 70% {
48 | box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
49 | }
50 | 100% {
51 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
52 | }
53 | }
--------------------------------------------------------------------------------
/src/pages/duty/DutyManageCreateModal.jsx:
--------------------------------------------------------------------------------
1 | import { createDutyManager, updateDutyManager } from '../../api/duty'
2 | import { getAllUsers } from '../../api/other'
3 | import { Modal, Form, Input, Button, Select } from 'antd'
4 | import React, { useState, useEffect } from 'react'
5 | const MyFormItemContext = React.createContext([])
6 |
7 | function toArr(str) {
8 | return Array.isArray(str) ? str : [str]
9 | }
10 |
11 | const MyFormItem = ({ name, ...props }) => {
12 | const prefixPath = React.useContext(MyFormItemContext)
13 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
14 | return
15 | }
16 |
17 | export const CreateDutyModal = ({ visible, onClose, handleList, selectedRow, type }) => {
18 | const [form] = Form.useForm()
19 | const { Option } = Select
20 | const [filteredOptions, setFilteredOptions] = useState([])
21 | const renderedOptions = new Set();
22 | const [selectedItems, setSelectedItems] = useState({})
23 |
24 | useEffect(() => {
25 | if (selectedRow) {
26 | form.setFieldsValue({
27 | name: selectedRow.name,
28 | description: selectedRow.description,
29 | manager: selectedRow.manager.username,
30 | })
31 | }
32 | }, [selectedRow, form])
33 |
34 |
35 | // 禁止输入空格
36 | const [spaceValue, setSpaceValue] = useState('')
37 |
38 | const handleInputChange = (e) => {
39 | // 移除输入值中的空格
40 | const newValue = e.target.value.replace(/\s/g, '')
41 | setSpaceValue(newValue)
42 | }
43 |
44 | const handleKeyPress = (e) => {
45 | // 阻止空格键的默认行为
46 | if (e.key === ' ') {
47 | e.preventDefault()
48 | }
49 | }
50 |
51 | const handleCreate = async (data) => {
52 | try {
53 | await createDutyManager(data)
54 | handleList()
55 | } catch (error) {
56 | console.error(error)
57 | }
58 | }
59 |
60 | const handleUpdate = async (data) => {
61 | try {
62 | await updateDutyManager(data)
63 | handleList()
64 | } catch (error) {
65 | console.error(error)
66 | }
67 | }
68 |
69 | const handleFormSubmit = async (values) => {
70 |
71 | const newData = {
72 | ...values,
73 | manager: {
74 | username: selectedItems.value,
75 | userid: selectedItems.userid,
76 | }
77 | }
78 |
79 | if (type === 'create') {
80 | handleCreate(newData)
81 | }
82 | if (type === 'update') {
83 | const newUpdateData = {
84 | ...newData,
85 | tenantId: selectedRow.tenantId,
86 | id: selectedRow.id,
87 | }
88 | handleUpdate(newUpdateData)
89 | }
90 |
91 | // 关闭弹窗
92 | onClose()
93 | }
94 |
95 | const handleSelectChange = (_, value) => {
96 | setSelectedItems(value)
97 | }
98 |
99 | const handleSearchDutyUser = async () => {
100 | try {
101 | const res = await getAllUsers()
102 | const options = res.data.map((item) => ({
103 | username: item.username,
104 | userid: item.userid
105 | }))
106 | setFilteredOptions(options)
107 | } catch (error) {
108 | console.error(error)
109 | }
110 | }
111 |
112 | const renderOption = (item) => {
113 | if (!renderedOptions.has(item.username)) {
114 | renderedOptions.add(item.username);
115 | return ;
116 | }
117 | return null; // 如果选项已存在,不渲染
118 | };
119 |
120 | return (
121 |
122 |
149 |
160 |
161 |
162 |
163 |
172 |
173 |
174 |
175 | )
176 | }
--------------------------------------------------------------------------------
/src/pages/duty/calendar/UpdateCalendar.jsx:
--------------------------------------------------------------------------------
1 | import { updateCalendar } from '../../../api/duty'
2 | import { getAllUsers } from '../../../api/other.jsx'
3 | import { Modal, Form, Button, message, Select } from 'antd'
4 | import React, { useState } from 'react'
5 |
6 | export const UpdateCalendarModal = ({ visible, onClose, time, tenantId, dutyId, date }) => {
7 | const { Option } = Select
8 | const [selectedItems, setSelectedItems] = useState([])
9 | const [filteredOptions, setFilteredOptions] = useState([])
10 |
11 | const handleSelectChange = (_, value) => {
12 | setSelectedItems(value)
13 | }
14 |
15 | const handleSearchDutyUser = async () => {
16 | try {
17 | const params = {
18 | "joinDuty": "true"
19 | }
20 | const res = await getAllUsers(params)
21 | const options = res.data.map((item) => ({
22 | username: item.username,
23 | userid: item.userid
24 | }))
25 | setFilteredOptions(options)
26 | } catch (error) {
27 | console.error(error)
28 | }
29 | }
30 |
31 | const handleUpdate = async (data) => {
32 | try {
33 | await updateCalendar(data)
34 | } catch (error) {
35 | console.error(error)
36 | }
37 | }
38 |
39 | const handleFormSubmit = async () => {
40 |
41 | const calendarData = {
42 | tenantId: tenantId,
43 | dutyId: dutyId,
44 | time: date,
45 | userid: selectedItems.userid,
46 | username: selectedItems.value,
47 | }
48 |
49 | await handleUpdate(calendarData)
50 |
51 | // 关闭弹窗
52 | onClose()
53 |
54 | }
55 |
56 | return (
57 |
58 |
59 |
60 | 调整值班人员, 当前值班日期: {time}
61 |
62 |
63 |
73 |
89 |
90 |
91 |
92 |
102 |
103 |
104 | )
105 | }
--------------------------------------------------------------------------------
/src/pages/duty/calendar/index.css:
--------------------------------------------------------------------------------
1 | .ant-calendar {
2 | border-radius: 12px !important;
3 | }
4 |
5 | .ant-calendar-fullscreen .ant-calendar {
6 | border-radius: 0 !important;
7 | }
8 |
9 | .ant-calendar-date {
10 | font-size: 14px !important;
11 | color: #333 !important;
12 | }
13 |
14 | .ant-calendar-header {
15 | background-color: #566b81 !important;
16 | }
17 |
18 | .ant-calendar-body {
19 | background-color: #421717 !important;
20 | }
21 |
22 | .text-xs {
23 | font-size: 12px;
24 | }
25 |
26 | .text-gray-500 {
27 | color: #fffefe;
28 | }
29 |
30 | .font-medium {
31 | font-weight: 500;
32 | }
33 |
34 | .text-blue-600 {
35 | color: #1dd8a0;
36 | }
37 |
38 | .shadow-sm:hover {
39 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
40 | }
41 |
42 | .rounded-lg {
43 | border-radius: 0.5rem;
44 | }
45 |
46 | .max-w-5xl {
47 | max-width: 96rem;
48 | }
49 |
50 | .mx-auto {
51 | margin-left: auto;
52 | margin-right: auto;
53 | }
--------------------------------------------------------------------------------
/src/pages/event/index.css:
--------------------------------------------------------------------------------
1 | /* Removes the background overlay on the Spin component */
2 | .ant-spin-nested-loading,
3 | .ant-spin-container,
4 | .ant-spin-blur {
5 | background: none !important;
6 | }
7 |
8 | /* Removes the default overlay */
9 | .ant-spin {
10 | background-color: transparent !important;
11 | }
12 |
--------------------------------------------------------------------------------
/src/pages/event/tmpl/index.jsx:
--------------------------------------------------------------------------------
1 | import ContentLayout from "components/ContentLayout";
2 | import {ContentNormalHeader} from "components/ContentNormalHeader";
3 | import React from "react";
4 |
5 | export const EventTmpl = (props) => {
6 |
7 | return (
8 | <>
9 | }
11 | children={
12 |
13 | {props.children}
14 |
15 | }
16 | />
17 | >
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/pages/faultCenter/index.css:
--------------------------------------------------------------------------------
1 | .ant-tabs .ant-tabs-nav {
2 | position: unset
3 | }
--------------------------------------------------------------------------------
/src/pages/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .frm-vh-md-100 {
6 | height: 100vh;
7 | }
8 |
9 | .custom-svg path {
10 | fill: white;
11 | }
12 |
13 | * {
14 | scrollbar-width: none;
15 | -ms-overflow-style: none;
16 | }
17 |
18 | .font-bold {
19 | font-weight: 700;
20 | }
21 |
22 | .text-3xl {
23 | font-size: 1.875rem;
24 | line-height: 2.25rem;
25 | }
26 |
27 | .mb-2 {
28 | margin-bottom: 0.5rem;
29 | }
30 |
31 | .p-10 {
32 | padding: 2.5rem;
33 | }
34 |
35 | .flex-col {
36 | flex-direction: column;
37 | }
38 |
39 | .w-3\/5 {
40 | width: 60%;
41 | }
42 |
43 | .flex {
44 | display: flex
45 | ;
46 | }
47 |
48 | * {
49 | scrollbar-width: none;
50 | -ms-overflow-style: none;
51 | }
52 |
53 | *, ::before, ::after {
54 | --tw-translate-x: 0;
55 | --tw-translate-y: 0;
56 | --tw-rotate: 0;
57 | --tw-skew-x: 0;
58 | --tw-skew-y: 0;
59 | --tw-scale-x: 1;
60 | --tw-scale-y: 1;
61 | --tw-pan-x: ;
62 | --tw-pan-y: ;
63 | --tw-pinch-zoom: ;
64 | --tw-scroll-snap-strictness: proximity;
65 | --tw-ordinal: ;
66 | --tw-slashed-zero: ;
67 | --tw-numeric-figure: ;
68 | --tw-numeric-spacing: ;
69 | --tw-numeric-fraction: ;
70 | --tw-ring-inset: ;
71 | --tw-ring-offset-width: 0px;
72 | --tw-ring-offset-color: #fff;
73 | --tw-ring-color: rgb(59 130 246 / 0.5);
74 | --tw-ring-offset-shadow: 0 0 #0000;
75 | --tw-ring-shadow: 0 0 #0000;
76 | --tw-shadow: 0 0 #0000;
77 | --tw-shadow-colored: 0 0 #0000;
78 | --tw-blur: ;
79 | --tw-brightness: ;
80 | --tw-contrast: ;
81 | --tw-grayscale: ;
82 | --tw-hue-rotate: ;
83 | --tw-invert: ;
84 | --tw-saturate: ;
85 | --tw-sepia: ;
86 | --tw-drop-shadow: ;
87 | --tw-backdrop-blur: ;
88 | --tw-backdrop-brightness: ;
89 | --tw-backdrop-contrast: ;
90 | --tw-backdrop-grayscale: ;
91 | --tw-backdrop-hue-rotate: ;
92 | --tw-backdrop-invert: ;
93 | --tw-backdrop-opacity: ;
94 | --tw-backdrop-saturate: ;
95 | --tw-backdrop-sepia: ;
96 | }
97 |
98 | ::before, ::after {
99 | --tw-content: '';
100 | }
101 |
102 | *, ::before, ::after {
103 | box-sizing: border-box;
104 | border-width: 0;
105 | border-style: solid;
106 | border-color: #e5e7eb;
107 | }
108 |
109 | .transition-colors {
110 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
111 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
112 | transition-duration: 150ms;
113 | }
114 |
115 | .text-white {
116 | --tw-text-opacity: 1;
117 | color: rgb(255 255 255 / var(--tw-text-opacity));
118 | }
119 |
120 | .text-white {
121 | --tw-text-opacity: 1;
122 | color: rgb(255 255 255 / var(--tw-text-opacity));
123 | }
124 |
125 | .py-3 {
126 | padding-top: 0.75rem;
127 | padding-bottom: 0.75rem;
128 | }
129 |
130 | .rounded-full {
131 | border-radius: 9999px;
132 | }
133 |
134 | .w-full {
135 | width: 100%;
136 | }
137 |
138 | body {
139 | margin:0px
140 | }
141 |
142 | #components-form-demo-normal-login .login-form {
143 | max-width: 300px;
144 | }
145 | #components-form-demo-normal-login .login-form-forgot {
146 | float: right;
147 | }
148 | #components-form-demo-normal-login .ant-col-rtl .login-form-forgot {
149 | float: left;
150 | }
151 | #components-form-demo-normal-login .login-form-button {
152 | width: 100%;
153 | }
154 |
155 | body {
156 | margin: 0;
157 | padding: 0;
158 | box-sizing: border-box;
159 | }
160 |
161 | .form-checkbox {
162 | appearance: none;
163 | padding: 0;
164 | print-color-adjust: exact;
165 | display: inline-block;
166 | vertical-align: middle;
167 | background-origin: border-box;
168 | user-select: none;
169 | flex-shrink: 0;
170 | height: 1rem;
171 | width: 1rem;
172 | border: 2px solid #D1D5DB;
173 | border-radius: 0.25rem;
174 | }
175 |
176 | .form-checkbox:checked {
177 | background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
178 | border-color: #000000;
179 | background-color: #000000;
180 | background-position: center;
181 | background-repeat: no-repeat;
182 | background-size: 100% 100%;
183 | }
184 |
185 | .form-checkbox:focus {
186 | outline: none;
187 | border-color: #6B7280;
188 | }
189 |
190 | /*.ant-form-item .ant-form-item-label >label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {*/
191 | /* display: inline-block;*/
192 | /* margin-inline-end: 0;*/
193 | /* color: #ff4d4f;*/
194 | /* font-size: 14px;*/
195 | /* font-family: SimSun, sans-serif;*/
196 | /* line-height: 1;*/
197 | /* content: "";*/
198 | /*}*/
199 |
200 | .ant-radio-group.ant-radio-group-block {
201 | display: block;
202 | }
--------------------------------------------------------------------------------
/src/pages/members/role/UserRoleCreateModal.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import { Modal, Form, Input, Button, Transfer } from 'antd'
3 | import React, { useEffect, useState } from 'react'
4 | import { createRole, updateRole } from '../../../api/role'
5 | import { getPermissionsList } from '../../../api/permissions'
6 | const MyFormItemContext = React.createContext([])
7 |
8 | function toArr(str) {
9 | return Array.isArray(str) ? str : [str]
10 | }
11 |
12 | // 表单
13 | const MyFormItem = ({ name, ...props }) => {
14 | const prefixPath = React.useContext(MyFormItemContext)
15 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
16 | return
17 | }
18 |
19 | // 函数组件
20 | const UserRoleCreateModal = ({ visible, onClose, selectedRow, type, handleList }) => {
21 | const [form] = Form.useForm()
22 | const [mockData, setMockData] = useState([])
23 | const [targetKeys, setTargetKeys] = useState([])
24 | const [disabledPermission, setDisabledPermission] = useState(false)
25 |
26 | // 禁止输入空格
27 | const [spaceValue, setSpaceValue] = useState('')
28 |
29 | const handleInputChange = (e) => {
30 | // 移除输入值中的空格
31 | const newValue = e.target.value.replace(/\s/g, '')
32 | setSpaceValue(newValue)
33 | }
34 |
35 | const handleKeyPress = (e) => {
36 | // 阻止空格键的默认行为
37 | if (e.key === ' ') {
38 | e.preventDefault()
39 | }
40 | }
41 |
42 | useEffect(() => {
43 | if (selectedRow) {
44 | setTargetKeys(selectedRow.permissions)
45 | form.setFieldsValue({
46 | id: selectedRow.id,
47 | name: selectedRow.name,
48 | description: selectedRow.description,
49 | permissions: targetKeys,
50 | })
51 | if (selectedRow.name === 'admin') {
52 | setDisabledPermission(true)
53 | } else {
54 | setDisabledPermission(false)
55 | }
56 | }
57 | }, [selectedRow, form])
58 |
59 |
60 | // 提交
61 | const handleFormSubmit = async (values) => {
62 |
63 | if (type === 'create') {
64 | try {
65 | const params = {
66 | ...values,
67 | permissions: targetKeys
68 | }
69 |
70 | await createRole(params)
71 | handleList()
72 | } catch (error) {
73 | console.error(error)
74 | }
75 | }
76 |
77 | if (type === 'update') {
78 | const params = {
79 | ...values,
80 | id: selectedRow.id,
81 | permissions: targetKeys
82 | }
83 |
84 | await updateRole(params)
85 | handleList()
86 |
87 | }
88 |
89 | // 关闭弹窗
90 | onClose()
91 |
92 | }
93 |
94 | const fetchData = async () => {
95 | try {
96 | const response = await getPermissionsList()
97 | const data = response.data
98 | formatData(data) // 格式化数据
99 | } catch (error) {
100 | console.error(error)
101 | }
102 | }
103 |
104 | useEffect(() => {
105 | fetchData()
106 | }, [])
107 |
108 | const formatData = (data) => {
109 | const tempTargetKeys = []
110 | const tempMockData = data.map((item) => {
111 | const { key } = item
112 | const chosen = targetKeys.includes(key)
113 | if (chosen) {
114 | tempTargetKeys.push(key)
115 | }
116 | return {
117 | key,
118 | api: item.api,
119 | title: item.key,
120 | chosen,
121 | }
122 | })
123 |
124 | setMockData(tempMockData)
125 | setTargetKeys(tempTargetKeys)
126 | }
127 |
128 | const handleOnChange = (keys) => {
129 | const tempTargetKeys = keys.map((key) => {
130 | const item = mockData.find((item) => item.key === key)
131 | return { key, api: item ? item.api : undefined }
132 | })
133 | setTargetKeys(tempTargetKeys)
134 | }
135 |
136 | return (
137 |
138 |
184 |
185 | )
186 | }
187 |
188 | export default UserRoleCreateModal
--------------------------------------------------------------------------------
/src/pages/members/role/index.jsx:
--------------------------------------------------------------------------------
1 | import {Input, Table, Button, Popconfirm, Tooltip, Space} from 'antd';
2 | import React, { useState, useEffect } from 'react';
3 | import UserRoleCreateModal from './UserRoleCreateModal';
4 | import { deleteRole, getRoleList } from '../../../api/role';
5 | import {DeleteOutlined, EditOutlined} from "@ant-design/icons";
6 |
7 | const { Search } = Input;
8 |
9 | export const UserRole = () => {
10 | const [selectedRow, setSelectedRow] = useState(null);
11 | const [updateVisible, setUpdateVisible] = useState(false);
12 | const [visible, setVisible] = useState(false);
13 | const [list, setList] = useState([]);
14 |
15 | // 表头
16 | const columns = [
17 | {
18 | title: '角色名称',
19 | dataIndex: 'name',
20 | key: 'name',
21 | width: 'auto',
22 | },
23 | {
24 | title: '描述',
25 | dataIndex: 'description',
26 | key: 'description',
27 | width: 'auto',
28 | render: (text) => (!text ? '-' : text),
29 | },
30 | {
31 | title: '创建时间',
32 | dataIndex: 'create_at',
33 | key: 'create_at',
34 | width: 'auto',
35 | render: (text) => {
36 | const date = new Date(text * 1000);
37 | return date.toLocaleString();
38 | },
39 | },
40 | {
41 | title: '操作',
42 | dataIndex: 'operation',
43 | fixed: 'right',
44 | width: 120,
45 | render: (_, record) =>
46 | list.length >= 1 ? (
47 |
48 |
49 | }
52 | onClick={() => handleUpdateModalOpen(record)}
53 | style={{ color: "#1677ff" }}
54 | />
55 |
56 |
57 | handleDelete(record)}
60 | okText="确定"
61 | cancelText="取消"
62 | placement="left"
63 | >
64 | } style={{ color: "#ff4d4f" }} />
65 |
66 |
67 |
68 | ) : null,
69 | },
70 | ];
71 |
72 | const [height, setHeight] = useState(window.innerHeight);
73 |
74 | useEffect(() => {
75 | // 定义一个处理窗口大小变化的函数
76 | const handleResize = () => {
77 | setHeight(window.innerHeight);
78 | };
79 |
80 | // 监听窗口的resize事件
81 | window.addEventListener('resize', handleResize);
82 |
83 | // 在组件卸载时移除监听器
84 | return () => {
85 | window.removeEventListener('resize', handleResize);
86 | };
87 | }, []);
88 |
89 | const handleList = async () => {
90 | const res = await getRoleList();
91 | setList(res.data);
92 | };
93 |
94 | const handleDelete = async (record) => {
95 | const params = {
96 | id: record.id,
97 | };
98 | await deleteRole(params);
99 | handleList();
100 | };
101 |
102 | const handleModalClose = () => {
103 | setVisible(false);
104 | };
105 |
106 | const handleUpdateModalClose = () => {
107 | setUpdateVisible(false);
108 | };
109 |
110 | const handleUpdateModalOpen = (record) => {
111 | setSelectedRow(record);
112 | setUpdateVisible(true);
113 | };
114 |
115 | useEffect(() => {
116 | handleList();
117 | }, []);
118 |
119 | return (
120 | <>
121 |
122 |
131 |
132 |
133 |
134 |
135 |
142 |
143 |
144 |
(index % 2 === 0 ? "bg-white" : "bg-gray-50")}
157 | rowKey={(record) => record.id} // 设置行唯一键
158 | />
159 |
160 | >
161 | );
162 | };
--------------------------------------------------------------------------------
/src/pages/members/user/UserChangePass.jsx:
--------------------------------------------------------------------------------
1 | import { Modal, Form, Input, Button } from 'antd'
2 | import React from 'react'
3 | import { changeUserPass } from '../../../api/user'
4 |
5 |
6 | // 函数组件
7 | const UserChangePass = ({ visible, onClose, userid, username }) => {
8 |
9 | // 提交
10 | const handleFormSubmit = async (values) => {
11 | try {
12 | const params = {
13 | ...values,
14 | userid: userid
15 | }
16 | await changeUserPass(params)
17 | } catch (error) {
18 | console.error(error)
19 | }
20 | // 关闭弹窗
21 | onClose()
22 |
23 | }
24 |
25 | return (
26 |
27 |
28 | 用户名: {username}
29 |
30 |
42 |
43 |
44 |
45 | ({
56 | validator(_, value) {
57 | if (!value || getFieldValue('password') === value) {
58 | return Promise.resolve()
59 | }
60 | return Promise.reject(new Error('The new password that you entered do not match!'))
61 | },
62 | }),
63 | ]}
64 | >
65 |
66 |
67 |
68 |
69 |
78 |
79 |
80 |
81 |
82 |
83 | )
84 | }
85 |
86 | export default UserChangePass
--------------------------------------------------------------------------------
/src/pages/members/user/UserCreateModal.jsx:
--------------------------------------------------------------------------------
1 | import { Modal, Form, Input, Button, Select, Switch } from 'antd'
2 | import React, { useState, useEffect } from 'react'
3 | import { registerUser, updateUser } from '../../../api/user'
4 | import { getRoleList } from '../../../api/role'
5 |
6 |
7 | const MyFormItemContext = React.createContext([])
8 |
9 | function toArr(str) {
10 | return Array.isArray(str) ? str : [str]
11 | }
12 |
13 | // 表单
14 | const MyFormItem = ({ name, ...props }) => {
15 | const prefixPath = React.useContext(MyFormItemContext)
16 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
17 | return
18 | }
19 |
20 | // 函数组件
21 | const UserCreateModal = ({ visible, onClose, selectedRow, type, handleList }) => {
22 | const [form] = Form.useForm()
23 | const [checked, setChecked] = useState()
24 | const [username, setUsername] = useState("")
25 |
26 | // 禁止输入空格
27 | const [spaceValue, setSpaceValue] = useState('')
28 |
29 | const handleInputChange = (e) => {
30 | // 移除输入值中的空格
31 | const newValue = e.target.value.replace(/\s/g, '')
32 | setSpaceValue(newValue)
33 | }
34 |
35 | const handleKeyPress = (e) => {
36 | // 阻止空格键的默认行为
37 | if (e.key === ' ') {
38 | e.preventDefault()
39 | }
40 | }
41 |
42 | useEffect(() => {
43 | if (selectedRow) {
44 | if (type === 'update') {
45 | setChecked(selectedRow.joinDuty === 'true' ? true : false)
46 | setUsername(selectedRow.username)
47 | }
48 | form.setFieldsValue({
49 | username: selectedRow.username,
50 | phone: selectedRow.phone,
51 | email: selectedRow.email,
52 | joinDuty: selectedRow.joinDuty, // 修改这里
53 | dutyUserId: selectedRow.dutyUserId,
54 | role: selectedRow.role
55 | })
56 | }
57 | }, [selectedRow, form])
58 |
59 | // 创建
60 | const handleCreate = async (values) => {
61 | try {
62 | await registerUser(values)
63 | handleList()
64 | } catch (error) {
65 | console.error(error)
66 | }
67 | }
68 |
69 | // 更新
70 | const handleUpdate = async (values) => {
71 | try {
72 | await updateUser(values)
73 | handleList()
74 | } catch (error) {
75 | console.error(error)
76 | }
77 | }
78 |
79 | // 提交
80 | const handleFormSubmit = (values) => {
81 |
82 | if (type === 'create') {
83 | const newValues = {
84 | ...values,
85 | joinDuty: values.joinDuty ? "true" : "false",
86 | role: "app",
87 | }
88 | handleCreate(newValues)
89 |
90 | }
91 |
92 | if (type === 'update') {
93 | const newValues = {
94 | ...values,
95 | joinDuty: values.joinDuty ? "true" : "false",
96 | userid: selectedRow.userid,
97 | }
98 |
99 | handleUpdate(newValues)
100 | }
101 |
102 | // 关闭弹窗
103 | onClose()
104 |
105 | }
106 |
107 | const onChangeJoinDuty = (checked) => {
108 | setChecked(checked) // 修改这里
109 | }
110 |
111 | return (
112 |
113 |
193 |
194 | )
195 | }
196 |
197 | export default UserCreateModal
--------------------------------------------------------------------------------
/src/pages/notice/img/Email.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/notice/img/customhook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/notice/img/dingding.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/notice/img/qywechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/notice/notification-type-icon.jsx:
--------------------------------------------------------------------------------
1 | import { ReactComponent as FeiShuIcon } from "./img/feishu.svg"
2 | import { ReactComponent as DingdingIcon } from "./img/dingding.svg"
3 | import { ReactComponent as EmailIcon } from "./img/Email.svg"
4 | import { ReactComponent as WeChatIcon } from "./img/qywechat.svg"
5 | import { ReactComponent as CustomHookIcon } from "./img/customhook.svg"
6 |
7 | const NOTIFICATION_TYPES = {
8 | FeiShu: {
9 | icon: FeiShuIcon,
10 | label: "飞书",
11 | },
12 | DingDing: {
13 | icon: DingdingIcon,
14 | label: "钉钉",
15 | },
16 | Email: {
17 | icon: EmailIcon,
18 | label: "邮件",
19 | },
20 | WeChat: {
21 | icon: WeChatIcon,
22 | label: "企业微信",
23 | },
24 | CustomHook: {
25 | icon: CustomHookIcon,
26 | label: "自定义Hook",
27 | },
28 | }
29 |
30 | export const NotificationTypeIcon = ({ type }) => {
31 | const notificationType = NOTIFICATION_TYPES[type]
32 |
33 | if (!notificationType) {
34 | return "-"
35 | }
36 |
37 | const IconComponent = notificationType.icon
38 |
39 | return (
40 |
41 |
42 |
{notificationType.label}
43 |
44 | )
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/src/pages/probing/detail.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useState, useEffect } from "react";
4 | import ReactECharts from "echarts-for-react";
5 | import { Spin, Empty, Modal, Select } from "antd";
6 | import { ProbingGetHistory } from "../../api/probing";
7 |
8 | const { Option } = Select;
9 |
10 | // 支持的图表类型
11 | const CHART_TYPES = {
12 | line: "line",
13 | bar: "bar",
14 | };
15 |
16 | // 支持的时间范围选项(秒)
17 | const TIME_RANGES = [
18 | { label: "最近1小时", value: 3600 },
19 | { label: "最近6小时", value: 6 * 3600 },
20 | { label: "最近24小时", value: 24 * 3600 },
21 | ];
22 |
23 | // 支持的字段类型定义
24 | const FIELD_CONFIG = {
25 | Latency: {
26 | title: "延迟 (ms)",
27 | yAxisName: "延迟 (ms)",
28 | chartType: CHART_TYPES.line,
29 | dataKey: "Latency",
30 | formatTooltip: (val) => `${val} ms`,
31 | color: "#4096ff"
32 | },
33 | StatusCode: {
34 | title: "HTTP 状态码",
35 | yAxisName: "状态码",
36 | chartType: CHART_TYPES.bar,
37 | dataKey: "StatusCode",
38 | formatTooltip: (val) => `状态码: ${val}`,
39 | color: "#a6f6a2"
40 | },
41 | Telnet: {
42 | title: "TCP 连通性",
43 | yAxisName: "是否成功 (1=成功, 0=失败)",
44 | chartType: CHART_TYPES.bar,
45 | dataKey: "IsSuccessful",
46 | formatTooltip: (val) => val === true ? "成功" : "失败",
47 | color: "#a6f6a2"
48 | },
49 | PacketLoss: {
50 | title: "ICMP 丢包率",
51 | yAxisName: "丢包率",
52 | chartType: CHART_TYPES.line,
53 | dataKey: "PacketLoss",
54 | formatTooltip: (val) => `${val} %`,
55 | color: "#4096ff"
56 | },
57 | MinRtt: {
58 | title: "ICMP 最小耗时",
59 | yAxisName: "最小耗时",
60 | chartType: CHART_TYPES.line,
61 | dataKey: "MinRtt",
62 | formatTooltip: (val) => `${val} ms`,
63 | color: "#4096ff"
64 | },
65 | MaxRtt: {
66 | title: "ICMP 最大耗时",
67 | yAxisName: "最大耗时",
68 | chartType: CHART_TYPES.line,
69 | dataKey: "MaxRtt",
70 | formatTooltip: (val) => `${val} ms`,
71 | color: "#4096ff"
72 | },
73 | AvgRtt: {
74 | title: "ICMP 平均耗时",
75 | yAxisName: "平均耗时",
76 | chartType: CHART_TYPES.line,
77 | dataKey: "AvgRtt",
78 | formatTooltip: (val) => `${val} ms`,
79 | color: "#4096ff"
80 | },
81 | };
82 |
83 | export const DetailProbingHistory = ({ visible, onClose, row }) => {
84 | const [loading, setLoading] = useState(true);
85 | const [chartData, setChartData] = useState({ timestamps: [], values: [] });
86 | const [selectedRange, setSelectedRange] = useState(3600); // 默认时间范围
87 |
88 | const field = row?.probingEndpointConfig?.strategy?.field || "Latency";
89 | const config = FIELD_CONFIG[field] || FIELD_CONFIG.Latency;
90 |
91 | // 获取拨测历史数据
92 | const fetchProbingHistory = async () => {
93 | try {
94 | setLoading(true);
95 |
96 | const params = {
97 | ruleId: row?.ruleId,
98 | dateRange: selectedRange,
99 | };
100 |
101 | const response = await ProbingGetHistory(params);
102 |
103 | if (response.code === 200 && Array.isArray(response.data)) {
104 | const sortedData = response.data.sort((a, b) => a.timestamp - b.timestamp);
105 |
106 | const timestamps = sortedData.map(item =>
107 | new Date(item.timestamp * 1000).toLocaleTimeString()
108 | );
109 |
110 | const values = sortedData.map(item => item.value[config.dataKey]);
111 |
112 | setChartData({ timestamps, values });
113 | } else {
114 | setChartData({ timestamps: [], values: [] });
115 | }
116 | } catch (error) {
117 | console.error("获取拨测历史失败", error);
118 | } finally {
119 | setLoading(false);
120 | }
121 | };
122 |
123 | // 每次打开 Modal 或切换时间范围时刷新数据
124 | useEffect(() => {
125 | if (visible) {
126 | fetchProbingHistory();
127 | }
128 | }, [visible, selectedRange, field]);
129 |
130 | // 构造 ECharts 配置项
131 | const getOption = () => {
132 | return {
133 | tooltip: {
134 | trigger: "axis",
135 | formatter: (params) => {
136 | return `${params[0].axisValue}
${config.title}: ${config.formatTooltip(params[0].value)}`;
137 | },
138 | },
139 | xAxis: {
140 | type: "category",
141 | data: chartData.timestamps,
142 | name: "时间",
143 | axisLabel: {
144 | rotate: 45
145 | }
146 | },
147 | yAxis: {
148 | type: "value",
149 | name: config.yAxisName,
150 | },
151 | series: [
152 | {
153 | name: config.title,
154 | type: config.chartType,
155 | data: chartData.values,
156 | smooth: config.chartType === CHART_TYPES.line,
157 | itemStyle: { color: config.color },
158 | lineStyle: { color: config.color },
159 | showSymbol: config.chartType === CHART_TYPES.line,
160 | },
161 | ],
162 | grid: {
163 | left: "10%",
164 | right: "10%",
165 | bottom: "15%",
166 | top: "20%",
167 | }
168 | };
169 | };
170 |
171 | return (
172 |
179 |
180 |
191 |
192 |
193 |
194 | {chartData.timestamps.length > 0 ? (
195 |
196 | ) : (
197 |
198 |
199 |
200 | )}
201 |
202 |
203 | );
204 | };
--------------------------------------------------------------------------------
/src/pages/probing/index.css:
--------------------------------------------------------------------------------
1 |
2 | /* 状态容器 */
3 | .status-container {
4 | display: flex;
5 | align-items: center;
6 | gap: 8px;
7 | }
8 |
9 | /* 小圆点 */
10 | .status-dot {
11 | width: 8px;
12 | height: 8px;
13 | border-radius: 50%;
14 | position: relative;
15 | }
16 |
17 | /* 启用状态的小圆点 */
18 | .status-enabled {
19 | background-color: #52c41a; /* 绿色 */
20 | animation: pulse-enabled 1.5s infinite;
21 | }
22 |
23 | /* 禁用状态的小圆点 */
24 | .status-disabled {
25 | background-color: #ff4d4f; /* 红色 */
26 | animation: pulse-disabled 1.5s infinite;
27 | }
28 |
29 | /* 启用状态动态效果 */
30 | @keyframes pulse-enabled {
31 | 0% {
32 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.4);
33 | }
34 | 70% {
35 | box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
36 | }
37 | 100% {
38 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
39 | }
40 | }
41 |
42 | /* 禁用状态动态效果 */
43 | @keyframes pulse-disabled {
44 | 0% {
45 | box-shadow: 0 0 0 0 rgba(196, 26, 26, 0.4);
46 | }
47 | 70% {
48 | box-shadow: 0 0 0 10px rgba(82, 196, 26, 0);
49 | }
50 | 100% {
51 | box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
52 | }
53 | }
--------------------------------------------------------------------------------
/src/pages/promethues/index.css:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | }
4 |
5 | .cm-expression-input {
6 | width: 100%;
7 | min-height: 32px;
8 | padding: 1px 10px;
9 | background-color: #ffffff;
10 | border: 1px solid #d9d9d9;
11 | border-radius: 6px;
12 | box-sizing: border-box;
13 | position: relative;
14 | display: flex;
15 | flex-direction: column;
16 | }
17 |
18 | .cm-scroller {
19 | overflow: visible !important;
20 | min-height: 32px;
21 | height: auto !important;
22 | padding: 0;
23 | }
24 |
25 | .cm-content {
26 | min-height: 32px;
27 | height: auto !important;
28 | word-wrap: break-word;
29 | white-space: pre-wrap;
30 | padding: 0;
31 | }
32 |
33 | .cm-line {
34 | padding: 0;
35 | }
36 |
37 | .promInputContent {
38 | width: 100%;
39 | display: flex;
40 | flex-direction: row;
41 | text-align: left;
42 | align-items: flex-start;
43 | justify-content: space-between;
44 |
45 | .promInput {
46 | min-height: 40px;
47 | box-sizing: border-box;
48 | margin: 0;
49 | padding: 4px 11px;
50 | color: rgba(0, 0, 0, 0.88);
51 | font-size: 14px;
52 | line-height: 1.5714285714285714;
53 | list-style: none;
54 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
55 | 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
56 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
57 | 'Noto Color Emoji';
58 | position: relative;
59 | display: inline-block;
60 | width: 100%;
61 | min-width: 0;
62 | background-color: #ffffff;
63 | background-image: none;
64 | border-width: 1px;
65 | border-style: solid;
66 | border-color: #d9d9d9;
67 | border-radius: 6px 0 0 6px;
68 | transition: all 0.2s;
69 | &:hover {
70 | border-color: #2877fb;
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/pages/settings/index.css:
--------------------------------------------------------------------------------
1 | .systemSettingsAnchorContainer {
2 | flex: 1;
3 | display: flex;
4 | flex-direction: row;
5 | justify-content: flex-end;
6 | }
--------------------------------------------------------------------------------
/src/pages/tenant/CreateTenant.jsx:
--------------------------------------------------------------------------------
1 | import { Modal, Form, Input, Button, Divider } from 'antd'
2 | import React, { useEffect } from 'react'
3 | import { createTenant, updateTenant } from '../../api/tenant'
4 |
5 | const MyFormItemContext = React.createContext([])
6 |
7 | function toArr(str) {
8 | return Array.isArray(str) ? str : [str]
9 | }
10 |
11 | // 表单
12 | const MyFormItem = ({ name, ...props }) => {
13 | const prefixPath = React.useContext(MyFormItemContext)
14 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
15 | return
16 | }
17 |
18 | export const CreateTenant = ({ visible, onClose, selectedRow, type, handleList }) => {
19 | const [form] = Form.useForm()
20 |
21 | useEffect(() => {
22 | if (selectedRow) {
23 | form.setFieldsValue({
24 | name: selectedRow.name,
25 | manager: selectedRow.manager,
26 | description: selectedRow.description,
27 | })
28 | }
29 | }, [selectedRow, form])
30 |
31 | // 创建
32 | const handleCreate = async (data) => {
33 | try {
34 | await createTenant(data)
35 | handleList()
36 | } catch (error) {
37 | console.error(error)
38 | }
39 | }
40 |
41 | // 更新
42 | const handleUpdate = async (data) => {
43 | try {
44 | await updateTenant(data)
45 | handleList()
46 | } catch (error) {
47 | console.error(error)
48 | }
49 | }
50 |
51 | // 提交
52 | const handleFormSubmit = async (values) => {
53 |
54 | if (type === 'create') {
55 | const params = {
56 | ...values,
57 | userNumber: 10,
58 | ruleNumber: 50,
59 | dutyNumber: 10,
60 | noticeNumber: 10,
61 | removeProtection: false,
62 | }
63 |
64 | await handleCreate(params)
65 | }
66 |
67 | if (type === 'update') {
68 | const params = {
69 | ...values,
70 | id: selectedRow.id,
71 | }
72 |
73 | await handleUpdate(params)
74 | }
75 |
76 | // 关闭弹窗
77 | onClose()
78 | }
79 |
80 | return (
81 | <>
82 |
83 |
145 |
146 | >
147 | )
148 | }
--------------------------------------------------------------------------------
/src/pages/tenant/detail.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import {Descriptions,Tabs} from "antd";
3 | import {TenantSecurity} from "./security";
4 | import {TenantQuota} from "./quota";
5 | import {TenantUsers} from "./users";
6 | import { useParams } from 'react-router-dom'
7 | import {getTenant} from "../../api/tenant";
8 |
9 | export const TenantDetail = ()=>{
10 | const { id } = useParams()
11 | const [tenantInfo,setTenantInfo] = useState({})
12 |
13 | useEffect(() => {
14 | handleGetTenantInfo();
15 | }, []);
16 |
17 | const handleGetTenantInfo = async ()=>{
18 | try {
19 | const params = {
20 | id: id,
21 | }
22 | const res = await getTenant(params)
23 | setTenantInfo(res.data);
24 | } catch (error) {
25 | console.error(error)
26 | }
27 | }
28 |
29 | const formatTimestamp = (timestamp) => {
30 | const date = new Date(timestamp * 1000); // Multiply by 1000 to convert seconds to milliseconds
31 | const year = date.getFullYear();
32 | const month = (date.getMonth() + 1).toString().padStart(2, '0'); // Months are zero-based
33 | const day = date.getDate().toString().padStart(2, '0');
34 | const hours = date.getHours().toString().padStart(2, '0');
35 | const minutes = date.getMinutes().toString().padStart(2, '0');
36 | const seconds = date.getSeconds().toString().padStart(2, '0');
37 | return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
38 | };
39 |
40 | const itemsDescriptions = [
41 | {
42 | key: '1',
43 | label: '租户ID',
44 | children: tenantInfo.id,
45 | },
46 | {
47 | key: '2',
48 | label: '租户名称',
49 | children: tenantInfo.name,
50 | },
51 | {
52 | key: '3',
53 | label: '负责人',
54 | children: tenantInfo.manager,
55 | },
56 | {
57 | key: '4',
58 | label: '描述',
59 | children: tenantInfo.description,
60 | },
61 | {
62 | key: '4',
63 | label: '创建人',
64 | children: tenantInfo.createBy,
65 | },
66 | {
67 | key: '5',
68 | label: '创建时间',
69 | children: formatTimestamp(tenantInfo.createAt),
70 | },
71 | ];
72 |
73 | const itemsTabs = [
74 | {
75 | key: '1',
76 | label: '配额管理',
77 | children: ,
78 | },
79 | {
80 | key: '2',
81 | label: '成员管理',
82 | children: ,
83 | },
84 | {
85 | key: '3',
86 | label: '安全配置',
87 | children: ,
88 | },
89 | ];
90 | return(
91 |
92 |
93 |
94 |
95 | 高级配置
96 |
97 |
98 |
99 | )
100 | }
--------------------------------------------------------------------------------
/src/pages/tenant/index.css:
--------------------------------------------------------------------------------
1 | /* TenantUsers.css */
2 | .ant-transfer-list-content-item-disabled {
3 | pointer-events: none; /* Prevent clicks */
4 | cursor: not-allowed; /* Show non-interactive cursor */
5 | }
6 |
7 | .disable-right-transfer .ant-transfer-list:last-child {
8 | pointer-events: none; /* Prevent any interaction */
9 | background-color: #f5f5f5; /* Optional: Change background to indicate disabled state */
10 | opacity: 0.8; /* Optional: Slightly fade the right box */
11 | }
12 |
13 | .ant-transfer .ant-transfer-list-content-item-remove{
14 | color: #f5f5f5;
15 | }
--------------------------------------------------------------------------------
/src/pages/tenant/quota.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import {Button, Form, Input, Popconfirm} from "antd";
3 | import { updateTenant } from '../../api/tenant'
4 | const MyFormItemContext = React.createContext([])
5 |
6 | function toArr(str) {
7 | return Array.isArray(str) ? str : [str]
8 | }
9 |
10 | // 表单
11 | const MyFormItem = ({ name, ...props }) => {
12 | const prefixPath = React.useContext(MyFormItemContext)
13 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
14 | return
15 | }
16 |
17 | export const TenantQuota = ({tenantInfo})=>{
18 | const [form] = Form.useForm()
19 |
20 | useEffect(() => {
21 | console.log(tenantInfo)
22 | if (tenantInfo) {
23 | form.setFieldsValue({
24 | userNumber: Number(tenantInfo.userNumber),
25 | ruleNumber: Number(tenantInfo.ruleNumber),
26 | dutyNumber: Number(tenantInfo.dutyNumber),
27 | noticeNumber: Number(tenantInfo.noticeNumber),
28 | })
29 | }
30 | }, [tenantInfo, form])
31 |
32 | const handleFormSubmit =async (values) =>{
33 | try {
34 | const params = {
35 | ...tenantInfo,
36 | userNumber: Number(values.userNumber),
37 | ruleNumber: Number(values.ruleNumber),
38 | dutyNumber: Number(values.dutyNumber),
39 | noticeNumber: Number(values.noticeNumber),
40 | }
41 |
42 | await updateTenant(params)
43 | } catch (error) {
44 | console.error(error)
45 | }
46 | }
47 |
48 | return(
49 |
131 | )
132 | }
--------------------------------------------------------------------------------
/src/pages/tenant/security.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import {Form, Switch} from "antd";
3 | import {updateTenant} from "../../api/tenant";
4 |
5 | const MyFormItemContext = React.createContext([])
6 |
7 | function toArr(str) {
8 | return Array.isArray(str) ? str : [str]
9 | }
10 |
11 | // 表单
12 | const MyFormItem = ({ name, ...props }) => {
13 | const prefixPath = React.useContext(MyFormItemContext)
14 | const concatName = name !== undefined ? [...prefixPath, ...toArr(name)] : undefined
15 | return
16 | }
17 |
18 | export const TenantSecurity = ({tenantInfo})=>{
19 | const [form] = Form.useForm()
20 | const [enabled, setEnabled] = useState(false)
21 |
22 | useEffect(() => {
23 | if (tenantInfo) {
24 | setEnabled(tenantInfo.removeProtection)
25 | }
26 | }, [tenantInfo, form])
27 |
28 | const handleSwitchChange = async (checked) => {
29 | setEnabled(checked);
30 | try {
31 | const params = {
32 | ...tenantInfo,
33 | removeProtection: checked,
34 | }
35 |
36 | await updateTenant(params)
37 | } catch (error) {
38 | console.error(error)
39 | }
40 | };
41 |
42 | return(
43 | <>
44 |
50 |
51 |
52 | >
53 | )
54 | }
--------------------------------------------------------------------------------
/src/routes/index.jsx:
--------------------------------------------------------------------------------
1 | import { AlertRuleList } from "../pages/alert/rule";
2 | import { AlertRuleGroup } from "../pages/alert/ruleGroup";
3 | import { RuleTemplate } from "../pages/alert/tmpl";
4 | import { RuleTemplateGroup } from "../pages/alert/tmplGroup";
5 | import { Datasources } from "../pages/datasources";
6 | import { DutyManage } from "../pages/duty";
7 | import { Home } from "../pages/home";
8 | import { UserRole } from "../pages/members/role";
9 | import { User } from "../pages/members/user";
10 | import { NoticeObjects } from "../pages/notice";
11 | import { NoticeTemplate } from "../pages/notice/tmpl";
12 | import { Silences } from "../pages/silence";
13 | import { Login } from "../pages/login";
14 | import Error from "../utils/Error"
15 | import { ComponentsContent } from '../components';
16 | import { Tenants } from "../pages/tenant";
17 | import { GrafanaDashboardComponent } from "../pages/dashboards/dashboard/iframe";
18 | import { DashboardFolder } from "../pages/dashboards/folder";
19 | import { AuditLog } from "../pages/audit";
20 | import { SystemSettings } from "../pages/settings";
21 | import { TenantDetail } from "../pages/tenant/detail";
22 | import { AlertRule } from "../pages/alert/rule/create";
23 | import {Dashboards} from "../pages/dashboards/dashboard";
24 | import {Subscribe} from "../pages/subscribe";
25 | import {CreateSubscribeModel} from "../pages/subscribe/create";
26 | import {NoticeRecords} from "../pages/notice/history";
27 | import {CalendarApp} from "../pages/duty/calendar";
28 | import {Probing} from "../pages/probing";
29 | import {CreateProbingRule} from "../pages/probing/create";
30 | import {OnceProbing} from "../pages/probing/once";
31 | import Profile from "../pages/profile";
32 | import {FaultCenter} from "../pages/faultCenter";
33 | import {FaultCenterDetail} from "../pages/faultCenter/detail";
34 |
35 | // eslint-disable-next-line import/no-anonymous-default-export
36 | export default [
37 | {
38 | path: '/',
39 | element: } />,
40 | },
41 | {
42 | path: '/login',
43 | element:
44 | },
45 | {
46 | path: '/ruleGroup',
47 | element: } />,
48 | },
49 | {
50 | path: '/ruleGroup/:id/rule/list',
51 | element: } />
52 | },
53 | {
54 | path: '/ruleGroup/:id/rule/add',
55 | element: } />
56 | },
57 | {
58 | path: '/ruleGroup/:id/rule/:ruleId/edit',
59 | element: } />
60 | },
61 | {
62 | path: '/silenceRules',
63 | element: } />
64 | },
65 | {
66 | path: '/tmplType/:tmplType/group',
67 | element: } />,
68 | },
69 | {
70 | path: '/tmplType/:tmplType/:ruleGroupName/templates',
71 | element: } />
72 | },
73 | {
74 | path: '/noticeObjects',
75 | element: } />
76 | },
77 | {
78 | path: '/noticeTemplate',
79 | element: } />
80 | },
81 | {
82 | path: '/noticeRecords',
83 | element: } />
84 | },
85 | {
86 | path: '/dutyManage',
87 | element: } />
88 | },
89 | {
90 | path: '/dutyManage/:id/calendar',
91 | element: } />
92 | },
93 | {
94 | path: '/user',
95 | element: } />
96 | },
97 | {
98 | path: '/userRole',
99 | element: } />
100 | },
101 | {
102 | path: '/tenants',
103 | element: } />
104 | },
105 | {
106 | path: '/tenants/detail/:id',
107 | element: } />
108 | },
109 | {
110 | path: '/datasource',
111 | element: } />
112 | },
113 | {
114 | path: '/folders',
115 | element: } />
116 | },
117 | {
118 | path: '/folder/:id/list',
119 | element: } />
120 | },
121 | {
122 | path: 'dashboard/f/:fid/g/:did/info',
123 | element: } />
124 | },
125 | {
126 | path: '/auditLog',
127 | element: } />
128 | },
129 | {
130 | path: '/settings',
131 | element: }/>
132 | },
133 | {
134 | path: '/onceProbing',
135 | element: } />
136 | },
137 | {
138 | path: '/probing',
139 | element: } />
140 | },
141 | {
142 | path: '/probing/create',
143 | element: } />
144 | },
145 | {
146 | path: '/probing/:id/edit',
147 | element: } />
148 | },
149 | {
150 | path: '/subscribes',
151 | element: } />
152 | },
153 | {
154 | path: '/subscribe/create',
155 | element: } />
156 | },
157 | {
158 | path: '/profile',
159 | element: } />
160 | },
161 | {
162 | path: '/faultCenter',
163 | element: } />
164 | },
165 | {
166 | path: '/faultCenter/detail/:id',
167 | element: } />
168 | },
169 | {
170 | path: '/*',
171 | element:
172 | }
173 | ]
--------------------------------------------------------------------------------
/src/utils/Auth.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useEffect, useState } from "react"
4 | import { useNavigate } from "react-router-dom"
5 | import { message } from "antd"
6 | import axios from "axios"
7 |
8 | // Create a Higher-Order Component (HOC) instead of a hook
9 | const Auth = (WrappedComponent) => {
10 | // Return a new component
11 | return function WithAuthComponent(props) {
12 | const navigate = useNavigate()
13 | const [errorCount, setErrorCount] = useState(0)
14 |
15 | // Check if user is logged in
16 | useEffect(() => {
17 | const checkUser = async () => {
18 | const token = localStorage.getItem("Authorization")
19 | if (!token) {
20 | navigate("/login") // Redirect to login page if not logged in
21 | }
22 | }
23 |
24 | checkUser()
25 | }, [navigate])
26 |
27 | // Set global request headers
28 | useEffect(() => {
29 | const token = localStorage.getItem("Authorization")
30 | if (token) {
31 | axios.defaults.headers.common["Authorization"] = `Bearer ${token}`
32 | }
33 | }, [])
34 |
35 | // Response interceptor
36 | useEffect(() => {
37 | const interceptor = axios.interceptors.response.use(
38 | (response) => response,
39 | (error) => {
40 | if (error.response?.status === 401) {
41 | setErrorCount((prevCount) => prevCount + 1)
42 | }
43 | return Promise.reject(error)
44 | }
45 | )
46 |
47 | return () => {
48 | axios.interceptors.response.eject(interceptor) // Clean up interceptor
49 | }
50 | }, [])
51 |
52 | // Check error count and show message
53 | useEffect(() => {
54 | if (errorCount > 0) {
55 | localStorage.clear()
56 | navigate("/login") // Redirect to login page
57 | message.error("登录已过期,请重新登录")
58 | }
59 | }, [errorCount, navigate])
60 |
61 | // Render the wrapped component with all props
62 | return
63 | }
64 | }
65 |
66 | export default Auth
67 |
--------------------------------------------------------------------------------
/src/utils/Error.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Empty } from 'antd'
3 | const Error = () =>
4 | export default Error
--------------------------------------------------------------------------------
/src/utils/MarkdownRenderer.css:
--------------------------------------------------------------------------------
1 | /* 标题层级系统 */
2 | .h1 { font-size: 2.4rem; color: #2c3e50; margin: 2rem 0; border-bottom: 3px solid #ecf0f1; }
3 | .h2 { font-size: 2rem; color: #34495e; margin: 1.8rem 0; }
4 | .h3 { font-size: 1.7rem; color: #16a085; margin: 1.6rem 0; }
5 | .h4 { font-size: 1.5rem; color: #8e44ad; margin: 1.4rem 0; }
6 | .h5 { font-size: 1.3rem; color: #c0392b; margin: 1.2rem 0; }
7 | .h6 { font-size: 1.1rem; color: #7f8c8d; margin: 1rem 0; }
8 |
9 | /* 精确换行处理 */
10 | .markdown-p {
11 | white-space: pre-wrap; /* 保留换行符 */
12 | line-height: 1.8;
13 | margin: 1.2em 0;
14 | }
15 |
16 | /* 代码块换行保留 */
17 | pre {
18 | code {
19 | white-space: pre-wrap !important;
20 | word-break: break-word;
21 | }
22 | }
23 |
24 | /* 列表项换行 */
25 | li {
26 | p {
27 | margin: 0.5em 0;
28 | white-space: pre-wrap;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/See.jsx:
--------------------------------------------------------------------------------
1 | export const See = ({ className = "w-full h-full" }) => (
2 |
41 | );
42 |
--------------------------------------------------------------------------------
/src/utils/copyToClipboard.jsx:
--------------------------------------------------------------------------------
1 | import {message} from "antd";
2 |
3 | export const copyToClipboard = (text) => {
4 | navigator.clipboard.writeText(text).then(
5 | () => {
6 | message.success('ID 已复制到剪贴板');
7 | },
8 | () => {
9 | message.error('复制失败');
10 | }
11 | );
12 | };
--------------------------------------------------------------------------------
/src/utils/http.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 网络请求配置
3 | */
4 | import axios from 'axios';
5 | import {message} from "antd";
6 | const protocol = window.location.protocol;
7 | const curUrl = window.location.hostname
8 | const port = window.location.port;
9 | const type = process.env.REACT_APP_TYPE
10 | axios.defaults.timeout = 100000;
11 |
12 | if (type === "local") {
13 | axios.defaults.baseURL = `${protocol}//${curUrl}:${9001}`; // 本地开发端口为 9001
14 | } else {
15 | axios.defaults.baseURL = `${protocol}//${curUrl}:${port}`; // 使用当前端口
16 | }
17 | /**
18 | * http request 拦截器
19 | */
20 | axios.interceptors.request.use(
21 | (config) => {
22 | //@ts-ignore
23 | config.headers = {
24 | 'Content-Type': 'application/json',
25 | 'TenantID': localStorage.getItem('TenantID'),
26 | };
27 | if (localStorage.getItem('Authorization')) {
28 | config.headers.Authorization = `Bearer ${localStorage.getItem('Authorization')}`;
29 | }
30 |
31 | return config;
32 | },
33 | (error) => {
34 | return Promise.reject(error);
35 | }
36 | );
37 |
38 | /**
39 | * http response 拦截器
40 | */
41 | axios.interceptors.response.use(
42 | (response) => {
43 | return response;
44 | },
45 | (error) => {
46 | switch (error?.response?.status){
47 | case 401:
48 | window.localStorage.removeItem('Authorization');
49 | window.history.replaceState(null, '', '/login');
50 | // window.location.reload();
51 | case 403:
52 | message.error("无权限访问!")
53 | window.history.replaceState(null, '', '/');
54 | }
55 |
56 | return Promise.reject(error);
57 | }
58 | );
59 |
60 | /**
61 | * 封装get方法
62 | * @param url 请求url
63 | * @param params 请求参数
64 | * @returns {Promise}
65 | */
66 | export function get(url, params = {}) {
67 | return new Promise((resolve, reject) => {
68 | axios
69 | .get(url, {
70 | params: params,
71 | })
72 | .then((response) => {
73 | resolve(response.data);
74 | })
75 | .catch((error) => {
76 | reject(error);
77 | });
78 | });
79 | }
80 |
81 | /**
82 | * 封装post请求
83 | * @param url
84 | * @param data
85 | * @returns {Promise}
86 | */
87 |
88 | export function post(url, data) {
89 | return new Promise((resolve, reject) => {
90 | axios.post(url, data).then(
91 | (response) => {
92 | //关闭进度条
93 | resolve(response.data);
94 | },
95 | (err) => {
96 | reject(err);
97 | }
98 | );
99 | });
100 | }
101 |
102 | /**
103 | * 封装patch请求
104 | * @param url
105 | * @param data
106 | * @returns {Promise}
107 | */
108 | export function patch(url, data = {}) {
109 | return new Promise((resolve, reject) => {
110 | axios.patch(url, data).then(
111 | (response) => {
112 | resolve(response.data);
113 | },
114 | (err) => {
115 | msag(err);
116 | reject(err);
117 | }
118 | );
119 | });
120 | }
121 |
122 | /**
123 | * 封装put请求
124 | * @param url
125 | * @param data
126 | * @returns {Promise}
127 | */
128 |
129 | export function put(url, data = {}) {
130 | return new Promise((resolve, reject) => {
131 | axios.put(url, data).then(
132 | (response) => {
133 | resolve(response.data);
134 | },
135 | (err) => {
136 | msag(err);
137 | reject(err);
138 | }
139 | );
140 | });
141 | }
142 |
143 | //统一接口处理,返回数据
144 | // eslint-disable-next-line import/no-anonymous-default-export
145 | export default function (method, url, param) {
146 | return new Promise((resolve, reject) => {
147 | switch (method) {
148 | case 'get':
149 | get(url, param)
150 | .then(function (response) {
151 | resolve(response);
152 | })
153 | .catch(function (error) {
154 | reject(error);
155 | });
156 | break;
157 | case 'post':
158 | post(url, param)
159 | .then(function (response) {
160 | resolve(response);
161 | })
162 | .catch(function (error) {
163 | console.error('get request POST failed.', error);
164 | reject(error);
165 | });
166 | break;
167 | default:
168 | break;
169 | }
170 | });
171 | }
172 |
173 | //失败提示
174 | function msag(err) {
175 | if (err && err.response) {
176 | switch (err.response.status) {
177 | case 400:
178 | alert(err.response.data.error.details);
179 | break;
180 | case 401:
181 | alert('未授权,请登录');
182 | break;
183 |
184 | case 403:
185 | alert('拒绝访问');
186 | break;
187 |
188 | case 404:
189 | alert('请求地址出错');
190 | break;
191 |
192 | case 408:
193 | alert('请求超时');
194 | break;
195 |
196 | case 500:
197 | alert('服务器内部错误');
198 | break;
199 |
200 | case 501:
201 | alert('服务未实现');
202 | break;
203 |
204 | case 502:
205 | alert('网关错误');
206 | break;
207 |
208 | case 503:
209 | alert('服务不可用');
210 | break;
211 |
212 | case 504:
213 | alert('网关超时');
214 | break;
215 |
216 | case 505:
217 | alert('HTTP版本不受支持');
218 | break;
219 | default:
220 | }
221 | }
222 | }
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/**/*.{js,jsx,ts,tsx}",
5 | "./public/index.html"
6 | ],
7 | theme: {
8 | screens: {
9 | sm: '480px',
10 | md: '768px',
11 | lg: '976px',
12 | xl: '1440px',
13 | },
14 | fontFamily: {
15 | sans: ['Graphik', 'sans-serif'],
16 | serif: ['Merriweather', 'serif'],
17 | },
18 | extend: {
19 | spacing: {
20 | '128': '32rem',
21 | '144': '36rem',
22 | },
23 | borderRadius: {
24 | '4xl': '2rem',
25 | }
26 | }
27 | },
28 | darkMode: 'media', // 或者 'media'
29 | plugins: [],
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/w8t.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 |
4 | root /app;
5 | index index.html index.htm;
6 |
7 | location / {
8 | add_header Access-Control-Allow-Origin *;
9 | add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
10 | add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
11 |
12 | try_files $uri $uri/ /index.html;
13 | }
14 |
15 | location /api {
16 | add_header Access-Control-Allow-Origin *;
17 | add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
18 | add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
19 |
20 | # 优化超时设置,避免因请求处理时间过长导致超时
21 | proxy_connect_timeout 300;
22 | proxy_read_timeout 300;
23 | proxy_send_timeout 300;
24 |
25 | # 优化缓冲区设置,避免大请求体导致缓冲区不足
26 | proxy_buffer_size 128k;
27 | proxy_buffers 4 256k;
28 | proxy_busy_buffers_size 256k;
29 |
30 | # 关闭代理请求缓冲,确保大请求体直接传递到后端
31 | proxy_request_buffering off;
32 |
33 | # 配置SSL和代理相关参数
34 | proxy_ssl_server_name on;
35 | proxy_ssl_protocols TLSv1.2;
36 | proxy_pass http://w8t-service:9001;
37 | proxy_set_header Host $host;
38 | proxy_set_header X-Real-IP $remote_addr;
39 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
40 | proxy_set_header X-Forwarded-Proto $scheme;
41 | proxy_set_body $request_body;
42 | proxy_pass_request_headers on;
43 | }
44 |
45 | # 错误页面配置
46 | error_page 500 502 503 504 /500.html;
47 |
48 | # 全局请求体大小限制
49 | client_max_body_size 2050m;
50 | client_body_buffer_size 1024k;
51 |
52 | # 保持连接超时时间
53 | keepalive_timeout 10;
54 | }
--------------------------------------------------------------------------------