├── src-tauri ├── build.rs ├── .gitignore ├── icons │ ├── icon.ico │ ├── icon.png │ ├── 32x32.png │ ├── icon.icns │ ├── 128x128.png │ ├── StoreLogo.png │ ├── 128x128@2x.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ └── Square310x310Logo.png ├── gen │ └── schemas │ │ └── capabilities.json ├── capabilities │ └── main-capabilities.json ├── Cargo.toml ├── tauri.conf.json └── src │ ├── result.rs │ └── main.rs ├── .yarnrc.yml ├── src ├── components │ ├── Theme │ │ ├── blank.png │ │ └── index.jsx │ ├── DrawerHeader │ │ └── index.jsx │ ├── GridList │ │ └── index.jsx │ ├── LoadingBar │ │ └── index.jsx │ ├── PortInput │ │ └── index.jsx │ ├── App │ │ └── index.jsx │ ├── UpdateDialog │ │ └── index.jsx │ ├── AlertDialog │ │ └── index.jsx │ ├── Layout │ │ └── index.jsx │ └── TopBar │ │ └── index.jsx ├── main.css ├── main.jsx ├── reducers │ └── MainReducer │ │ ├── Actions │ │ ├── actionTypes.js │ │ └── index.js │ │ └── index.js ├── utils │ ├── ThemeSelector │ │ └── index.js │ └── Updater │ │ └── index.js ├── routes │ ├── About │ │ └── index.jsx │ ├── Settings │ │ └── index.jsx │ └── Home │ │ └── index.jsx ├── languages │ ├── zh_cn.json │ ├── ja_jp.json │ ├── en_us.json │ ├── nl_nl.json │ ├── es_es.json │ ├── it_it.json │ └── fr_fr.json └── contexts │ └── MainContextProvider │ └── index.jsx ├── .prettierignore ├── .prettierrc ├── vite.config.js ├── .gitignore ├── index.html ├── public └── favicon.svg ├── package.json ├── .github └── workflows │ ├── test.yml │ └── release.yml ├── .gitattributes ├── eslint.config.js ├── README.md └── LICENSE /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.11.0.cjs 4 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src/components/Theme/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src/components/Theme/blank.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .yarn 3 | dist 4 | node_modules 5 | nginx 6 | .idea 7 | README.md 8 | src-tauri 9 | .github 10 | -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDead/Advanced-PortChecker/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "tabWidth": 2, 5 | "semi": true, 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import { defineConfig } from 'vite'; 3 | import eslint from 'vite-plugin-eslint'; 4 | import svgr from 'vite-plugin-svgr'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [react(), eslint(), svgr()], 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/DrawerHeader/index.jsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | 3 | const DrawerHeader = styled('div')(({ theme }) => ({ 4 | display: 'flex', 5 | alignItems: 'center', 6 | justifyContent: 'flex-end', 7 | padding: theme.spacing(0, 1), 8 | ...theme.mixins.toolbar, 9 | })); 10 | 11 | export default DrawerHeader; 12 | -------------------------------------------------------------------------------- /.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 | /.yarn/install-state.gz 26 | -------------------------------------------------------------------------------- /src/components/GridList/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Grid from '@mui/material/Grid'; 3 | 4 | const GridList = ({ spacing, children, xs, md, lg }) => ( 5 | 6 | {children.map((e, i) => ( 7 | 8 | {e} 9 | 10 | ))} 11 | 12 | ); 13 | 14 | export default GridList; 15 | -------------------------------------------------------------------------------- /src/components/LoadingBar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import LinearProgress from '@mui/material/LinearProgress'; 3 | 4 | const LoadingBar = ({ marginTop }) => ( 5 |
12 | 13 |
14 | ); 15 | 16 | export default LoadingBar; 17 | -------------------------------------------------------------------------------- /src-tauri/gen/schemas/capabilities.json: -------------------------------------------------------------------------------- 1 | {"main-capabilities":{"identifier":"main-capabilities","description":"Main capabilities for Advanced PortChecker","local":true,"windows":["main"],"permissions":["core:default","dialog:allow-open","dialog:allow-save","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","dialog:default","os:default"]}} -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Advanced PortChecker 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: 4 | -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 5 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: 12 | source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/PortInput/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextField from '@mui/material/TextField'; 3 | 4 | const PortInput = ({ label, port, disabled, onKeyDown, onChange }) => ( 5 | 19 | ); 20 | 21 | export default PortInput; 22 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './main.css'; 4 | import App from './components/App'; 5 | import '@fontsource/roboto/300.css'; 6 | import '@fontsource/roboto/400.css'; 7 | import '@fontsource/roboto/500.css'; 8 | import '@fontsource/roboto/700.css'; 9 | import MainContextProvider from './contexts/MainContextProvider'; 10 | 11 | ReactDOM.createRoot(document.getElementById('root')).render( 12 | 13 | 14 | 15 | 16 | , 17 | ); 18 | -------------------------------------------------------------------------------- /src-tauri/capabilities/main-capabilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "main-capabilities", 3 | "description": "Main capabilities for Advanced PortChecker", 4 | "local": true, 5 | "windows": [ 6 | "main" 7 | ], 8 | "permissions": [ 9 | "core:default", 10 | "dialog:allow-open", 11 | "dialog:allow-save", 12 | "os:allow-platform", 13 | "os:allow-version", 14 | "os:allow-os-type", 15 | "os:allow-family", 16 | "os:allow-arch", 17 | "os:allow-exe-extension", 18 | "os:allow-locale", 19 | "os:allow-hostname", 20 | "dialog:default", 21 | "os:default" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/App/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { lazy } from 'react'; 2 | import { createBrowserRouter, RouterProvider } from 'react-router-dom'; 3 | import Layout from '../Layout'; 4 | 5 | const Home = lazy(() => import('../../routes/Home')); 6 | const Settings = lazy(() => import('../../routes/Settings')); 7 | const About = lazy(() => import('../../routes/About')); 8 | 9 | const router = createBrowserRouter([ 10 | { 11 | element: , 12 | children: [ 13 | { 14 | index: true, 15 | path: '/', 16 | element: , 17 | }, 18 | { 19 | path: '/settings', 20 | element: , 21 | }, 22 | { 23 | path: '/about', 24 | element: , 25 | }, 26 | ], 27 | }, 28 | ]); 29 | 30 | // 4️⃣ RouterProvider added 31 | const App = () => ; 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /src/components/Theme/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Card from '@mui/material/Card'; 3 | import CardActionArea from '@mui/material/CardActionArea'; 4 | import CardContent from '@mui/material/CardContent'; 5 | import CardMedia from '@mui/material/CardMedia'; 6 | import Typography from '@mui/material/Typography'; 7 | import blank from './blank.png'; 8 | 9 | const Theme = ({ title, description, color, selected, onAction }) => { 10 | const action = onAction || null; 11 | 12 | return ( 13 | 14 | 15 | 23 | 24 | 25 | {title} 26 | 27 | 28 | {description} 29 | 30 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default Theme; 37 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "advanced-portchecker" 3 | version = "2.3.0" 4 | description = "A lightweight TCP/IP port scanner with an intuitive UI." 5 | authors = ["CodeDead "] 6 | license = "GPL-3.0-only" 7 | repository = "https://github.com/CodeDead/Advanced-PortChecker" 8 | default-run = "advanced-portchecker" 9 | edition = "2024" 10 | rust-version = "1.91.1" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "2.5.2", features = [] } 16 | 17 | [dependencies] 18 | serde_json = "1.0" 19 | serde = { version = "1.0", features = ["derive"] } 20 | tauri = { version = "2.9.3", features = [] } 21 | open = "5.3.2" 22 | chrono = "0.4.42" 23 | tauri-plugin-dialog = "2.4.2" 24 | tauri-plugin-os = "2.3.2" 25 | 26 | [features] 27 | # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. 28 | # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. 29 | # DO NOT REMOVE!! 30 | custom-protocol = [ "tauri/custom-protocol" ] 31 | 32 | [profile.release] 33 | codegen-units = 1 34 | lto = true 35 | opt-level = "s" 36 | panic = "abort" 37 | strip = true 38 | -------------------------------------------------------------------------------- /src/reducers/MainReducer/Actions/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const SET_LANGUAGE_INDEX = 'SET_LANGUAGE_INDEX'; 2 | export const SET_THEME_INDEX = 'SET_THEME_INDEX'; 3 | export const SET_THEME_TYPE = 'SET_THEME_TYPE'; 4 | export const RESET_STATE = 'RESET_STATE'; 5 | export const SET_PAGE_INDEX = 'SET_PAGE_INDEX'; 6 | export const SET_AUTO_UPDATE = 'SET_AUTO_UPDATE'; 7 | export const SET_UPDATE = 'SET_UPDATE'; 8 | export const SET_ERROR = 'SET_ERROR'; 9 | export const SET_LOADING = 'SET_LOADING'; 10 | export const SET_CHECKED_FOR_UPDATES = 'SET_CHECKED_FOR_UPDATES'; 11 | export const SET_COLOR_ON_DARK = 'SET_COLOR_ON_DARK'; 12 | export const SET_ADDRESSES = 'SET_ADDRESSES'; 13 | export const SET_START_PORT = 'SET_START_PORT'; 14 | export const SET_END_PORT = 'SET_END_PORT'; 15 | export const SET_IS_SCANNING = 'SET_IS_SCANNING'; 16 | export const SET_THREADS = 'SET_THREADS'; 17 | export const SET_TIMEOUT = 'SET_TIMEOUT'; 18 | export const SET_NO_CLOSED = 'SET_NO_CLOSED'; 19 | export const SET_NO_UNKNOWN = 'SET_NO_UNKNOWN'; 20 | export const SET_SORT = 'SET_SORT'; 21 | export const SET_SCAN_RESULTS = 'SET_SCAN_RESULTS'; 22 | export const SET_THEME_TOGGLE = 'SET_THEME_TOGGLE'; 23 | export const SET_EXPORT_NO_CLOSED = 'SET_EXPORT_NO_CLOSED'; 24 | export const SET_EXPORT_NO_UNKNOWN = 'SET_EXPORT_NO_UNKNOWN'; 25 | export const SET_IS_CANCELLING = 'SET_IS_CANCELLING'; 26 | -------------------------------------------------------------------------------- /src/utils/ThemeSelector/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | blue, 3 | lightBlue, 4 | red, 5 | green, 6 | lightGreen, 7 | purple, 8 | deepPurple, 9 | grey, 10 | orange, 11 | deepOrange, 12 | amber, 13 | brown, 14 | pink, 15 | indigo, 16 | cyan, 17 | teal, 18 | lime, 19 | yellow, 20 | } from '@mui/material/colors'; 21 | 22 | /** 23 | * Select the theme, depending on the theme index 24 | * @param index The theme index 25 | * @returns The required color scheme 26 | * @constructor 27 | */ 28 | const ThemeSelector = (index) => { 29 | switch (index) { 30 | case 1: 31 | return lightBlue; 32 | case 2: 33 | return red; 34 | case 3: 35 | return green; 36 | case 4: 37 | return lightGreen; 38 | case 5: 39 | return purple; 40 | case 6: 41 | return deepPurple; 42 | case 7: 43 | return grey; 44 | case 8: 45 | return orange; 46 | case 9: 47 | return deepOrange; 48 | case 10: 49 | return amber; 50 | case 11: 51 | return brown; 52 | case 12: 53 | return pink; 54 | case 13: 55 | return indigo; 56 | case 14: 57 | return cyan; 58 | case 15: 59 | return teal; 60 | case 16: 61 | return lime; 62 | case 17: 63 | return yellow; 64 | default: 65 | return blue; 66 | } 67 | }; 68 | 69 | export default ThemeSelector; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "advanced-portchecker", 3 | "private": true, 4 | "version": "2.3.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "format": "prettier --check .", 11 | "preview": "vite preview", 12 | "tdev": "yarn tauri dev", 13 | "tbuild": "yarn tauri build" 14 | }, 15 | "dependencies": { 16 | "@emotion/react": "^11.14.0", 17 | "@emotion/styled": "^11.14.1", 18 | "@fontsource/roboto": "^5.2.8", 19 | "@mui/icons-material": "^7.3.5", 20 | "@mui/material": "^7.3.5", 21 | "@mui/x-data-grid": "^8.19.0", 22 | "@tauri-apps/api": "^2.9.0", 23 | "@tauri-apps/plugin-dialog": "^2.4.2", 24 | "@tauri-apps/plugin-os": "^2.3.2", 25 | "react": "^19.2.0", 26 | "react-dom": "^19.2.0", 27 | "react-router-dom": "^7.9.6" 28 | }, 29 | "devDependencies": { 30 | "@eslint/js": "^9.39.1", 31 | "@tauri-apps/cli": "^2.9.4", 32 | "@types/react": "^19.2.6", 33 | "@types/react-dom": "^19.2.3", 34 | "@vitejs/plugin-react": "^5.1.1", 35 | "eslint": "^9.39.1", 36 | "eslint-config-react-app": "^7.0.1", 37 | "eslint-plugin-import": "^2.32.0", 38 | "eslint-plugin-react": "^7.37.5", 39 | "eslint-plugin-react-hooks": "^7.0.1", 40 | "eslint-plugin-react-refresh": "^0.4.24", 41 | "globals": "^16.5.0", 42 | "prettier": "^3.6.2", 43 | "vite": "^7.2.4", 44 | "vite-plugin-eslint": "^1.8.1", 45 | "vite-plugin-svgr": "^4.5.0" 46 | }, 47 | "packageManager": "yarn@4.11.0" 48 | } 49 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../node_modules/@tauri-apps/cli/schema.json", 3 | "build": { 4 | "beforeBuildCommand": "yarn build", 5 | "beforeDevCommand": "yarn dev", 6 | "frontendDist": "../dist", 7 | "devUrl": "http://localhost:5173" 8 | }, 9 | "bundle": { 10 | "active": true, 11 | "category": "Utility", 12 | "copyright": "Copyright © 2025 CodeDead", 13 | "targets": "all", 14 | "externalBin": [], 15 | "icon": [ 16 | "icons/32x32.png", 17 | "icons/128x128.png", 18 | "icons/128x128@2x.png", 19 | "icons/icon.icns", 20 | "icons/icon.ico" 21 | ], 22 | "windows": { 23 | "certificateThumbprint": null, 24 | "digestAlgorithm": "sha256", 25 | "timestampUrl": "" 26 | }, 27 | "longDescription": "Advanced PortChecker is a TCP/IP port scanner that allows you to check if a port is open or not", 28 | "macOS": { 29 | "entitlements": null, 30 | "exceptionDomain": "", 31 | "frameworks": [], 32 | "providerShortName": null, 33 | "signingIdentity": null 34 | }, 35 | "resources": [], 36 | "shortDescription": "A TCP/IP Port scanner", 37 | "linux": { 38 | "deb": { 39 | "depends": [] 40 | } 41 | } 42 | }, 43 | "productName": "advanced-portchecker", 44 | "mainBinaryName": "advanced-portchecker", 45 | "version": "2.3.0", 46 | "identifier": "com.codedead.advancedportchecker", 47 | "plugins": {}, 48 | "app": { 49 | "withGlobalTauri": true, 50 | "windows": [ 51 | { 52 | "fullscreen": false, 53 | "height": 880, 54 | "resizable": true, 55 | "title": "Advanced PortChecker", 56 | "width": 1200 57 | } 58 | ], 59 | "security": { 60 | "capabilities": [ 61 | "main-capabilities" 62 | ], 63 | "csp": null 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/UpdateDialog/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@mui/material/Button'; 3 | import Dialog from '@mui/material/Dialog'; 4 | import DialogActions from '@mui/material/DialogActions'; 5 | import DialogContent from '@mui/material/DialogContent'; 6 | import DialogContentText from '@mui/material/DialogContentText'; 7 | import DialogTitle from '@mui/material/DialogTitle'; 8 | 9 | const UpdateDialog = ({ 10 | downloadUrl, 11 | openWebsite, 12 | infoUrl, 13 | newVersion, 14 | onClose, 15 | updateAvailable, 16 | newVersionText, 17 | information, 18 | download, 19 | cancel, 20 | }) => { 21 | /** 22 | * Close the UpdateDialog 23 | */ 24 | const handleClose = () => { 25 | if (onClose) { 26 | onClose(); 27 | } 28 | }; 29 | 30 | /** 31 | * Open the information page 32 | */ 33 | const openInformation = () => { 34 | openWebsite(infoUrl); 35 | }; 36 | 37 | /** 38 | * Open the download page 39 | */ 40 | const openDownload = () => { 41 | openWebsite(downloadUrl); 42 | handleClose(); 43 | }; 44 | 45 | return ( 46 | 52 | {updateAvailable} 53 | 54 | 55 | {newVersionText.replace('{x}', newVersion)} 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | ); 67 | }; 68 | 69 | export default UpdateDialog; 70 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - development 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | test-eslint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v5 17 | - name: Install modules 18 | run: yarn 19 | 20 | - name: Run ESLint 21 | run: yarn lint 22 | 23 | - name: Run Prettier 24 | run: yarn format 25 | 26 | test-tauri: 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | include: 31 | - platform: 'macos-latest' 32 | args: '--target aarch64-apple-darwin' 33 | - platform: 'macos-latest' 34 | args: '--target x86_64-apple-darwin' 35 | - platform: 'ubuntu-22.04' 36 | args: '' 37 | - platform: 'ubuntu-22.04-arm' 38 | args: '' 39 | - platform: 'windows-latest' 40 | args: '' 41 | 42 | runs-on: ${{ matrix.platform }} 43 | steps: 44 | - uses: actions/checkout@v5 45 | 46 | - name: install dependencies (ubuntu only) 47 | if: startsWith(matrix.platform, 'ubuntu-22.04') 48 | run: | 49 | sudo apt-get update 50 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf 51 | 52 | - name: Setup node 53 | uses: actions/setup-node@v6 54 | with: 55 | node-version: lts/* 56 | cache: 'yarn' 57 | 58 | - name: Install Rust stable 59 | uses: dtolnay/rust-toolchain@stable 60 | with: 61 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} 62 | 63 | - name: Rust cache 64 | uses: swatinem/rust-cache@v2 65 | with: 66 | workspaces: './src-tauri -> target' 67 | 68 | - name: Install frontend dependencies 69 | run: yarn install 70 | 71 | - name: Build the app 72 | uses: tauri-apps/tauri-action@v0 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish-tauri: 11 | permissions: 12 | contents: write 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - platform: 'macos-latest' 18 | args: '--target aarch64-apple-darwin' 19 | - platform: 'macos-latest' 20 | args: '--target x86_64-apple-darwin' 21 | - platform: 'ubuntu-22.04' 22 | args: '' 23 | - platform: 'ubuntu-22.04-arm' 24 | args: '' 25 | - platform: 'windows-latest' 26 | args: '' 27 | 28 | runs-on: ${{ matrix.platform }} 29 | steps: 30 | - uses: actions/checkout@v5 31 | 32 | - name: install dependencies (ubuntu only) 33 | if: startsWith(matrix.platform, 'ubuntu-22.04') 34 | run: | 35 | sudo apt-get update 36 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf 37 | 38 | - name: setup node 39 | uses: actions/setup-node@v6 40 | with: 41 | node-version: lts/* 42 | cache: 'yarn' 43 | 44 | - name: install Rust stable 45 | uses: dtolnay/rust-toolchain@stable 46 | with: 47 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} 48 | 49 | - name: Rust cache 50 | uses: swatinem/rust-cache@v2 51 | with: 52 | workspaces: './src-tauri -> target' 53 | 54 | - name: Install frontend dependencies 55 | run: yarn install 56 | 57 | - name: Build the app 58 | uses: tauri-apps/tauri-action@v0 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | tagName: ${{ github.ref_name }} 63 | releaseName: 'Advanced PortChecker v__VERSION__' 64 | releaseBody: 'See the assets to download this version and install.' 65 | releaseDraft: true 66 | prerelease: false 67 | args: ${{ matrix.args }} 68 | -------------------------------------------------------------------------------- /src/routes/About/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react'; 2 | import Button from '@mui/material/Button'; 3 | import Card from '@mui/material/Card'; 4 | import CardContent from '@mui/material/CardContent'; 5 | import Container from '@mui/material/Container'; 6 | import Typography from '@mui/material/Typography'; 7 | import packageJson from '../../../package.json'; 8 | import { MainContext } from '../../contexts/MainContextProvider'; 9 | import { openWebSite, setPageIndex } from '../../reducers/MainReducer/Actions'; 10 | 11 | const About = () => { 12 | const [state, d1] = useContext(MainContext); 13 | const { languageIndex } = state; 14 | const language = state.languages[languageIndex]; 15 | 16 | /** 17 | * Open the license page 18 | */ 19 | const openLicense = () => { 20 | openWebSite('https://codedead.com/Software/gpl.pdf'); 21 | }; 22 | 23 | /** 24 | * Open the home page 25 | */ 26 | const openHomePage = () => { 27 | openWebSite('https://codedead.com/'); 28 | }; 29 | 30 | useEffect(() => { 31 | d1(setPageIndex(2)); 32 | // eslint-disable-next-line react-hooks/exhaustive-deps 33 | }, []); 34 | 35 | return ( 36 | 37 | 38 | {language.about} 39 | 40 | 41 | 42 | 43 | {language.aboutText 44 | .replace('{x}', packageJson.version) 45 | .replace('{year}', new Date().getFullYear())} 46 | 47 | 48 | 49 | 57 | 65 | 66 | ); 67 | }; 68 | 69 | export default About; 70 | -------------------------------------------------------------------------------- /src/components/AlertDialog/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@mui/material/Button'; 3 | import Dialog from '@mui/material/Dialog'; 4 | import DialogActions from '@mui/material/DialogActions'; 5 | import DialogContent from '@mui/material/DialogContent'; 6 | import DialogContentText from '@mui/material/DialogContentText'; 7 | import DialogTitle from '@mui/material/DialogTitle'; 8 | 9 | const AlertDialog = ({ 10 | open, 11 | title, 12 | content, 13 | onOk, 14 | onCancel, 15 | onClose, 16 | agreeLabel, 17 | cancelLabel, 18 | }) => { 19 | /** 20 | * Cancel action 21 | */ 22 | const cancel = () => { 23 | if (onCancel) { 24 | onCancel(); 25 | onClose(); 26 | } 27 | }; 28 | 29 | /** 30 | * Agree action 31 | */ 32 | const agree = () => { 33 | if (onOk) { 34 | onOk(); 35 | onClose(); 36 | } 37 | }; 38 | 39 | /** 40 | * Close the dialog 41 | */ 42 | const close = () => { 43 | if (onClose) { 44 | onClose(); 45 | } 46 | }; 47 | 48 | const splitContent = content.split('\n').map((item, idx) => { 49 | if (idx === 0) { 50 | return ( 51 | 52 | {item} 53 | {content.split('\n').length > 1 ?
: null} 54 |
55 | ); 56 | } 57 | return ( 58 | 59 | {item.charAt(0).toUpperCase() + item.slice(1)} 60 |
61 |
62 | ); 63 | }); 64 | 65 | return ( 66 | 72 | {title} 73 | 74 | 75 | {splitContent} 76 | 77 | 78 | 79 | {onCancel ? : null} 80 | {onOk ? ( 81 | 84 | ) : null} 85 | 86 | 87 | ); 88 | }; 89 | 90 | export default AlertDialog; 91 | -------------------------------------------------------------------------------- /src/utils/Updater/index.js: -------------------------------------------------------------------------------- 1 | const Updater = (os, architectureName, currentVersion) => { 2 | /** 3 | * Compare two semantic versions 4 | * @param ver1 Version 1 5 | * @param ver2 Version 2 6 | * @returns {number} 1 if ver1 > ver2, -1 if ver1 < ver2, 0 if equal 7 | */ 8 | const semverCompare = (ver1, ver2) => { 9 | const v1Parts = ver1.slice(1).split('.').map(Number); 10 | const v2Parts = ver2.slice(1).split('.').map(Number); 11 | 12 | for (let i = 0; i < 3; i++) { 13 | if (v1Parts[i] > v2Parts[i]) return 1; 14 | if (v1Parts[i] < v2Parts[i]) return -1; 15 | } 16 | return 0; 17 | }; 18 | 19 | /** 20 | * Parse the update data 21 | * @param data The update data 22 | * @returns {{updateUrl, infoUrl: *, version: SemVer, updateAvailable: boolean}} The parsed update data 23 | */ 24 | const parseUpdate = (data) => { 25 | const platform = data.platforms.find( 26 | (p) => p.platformName.toLowerCase() === os.toLowerCase(), 27 | ); 28 | if (!platform) { 29 | throw new Error(`Platform ${os} not found`); 30 | } 31 | 32 | // Find the architecture 33 | const architecture = platform.architectures.find( 34 | (a) => a.name === architectureName, 35 | ); 36 | if (!architecture) { 37 | throw new Error( 38 | `Architecture ${architectureName} not found for platform ${os}`, 39 | ); 40 | } 41 | 42 | // Sort releases by semver in descending order 43 | const sortedReleases = architecture.releases.sort((a, b) => { 44 | return semverCompare(b.semver, a.semver); 45 | }); 46 | 47 | return { 48 | updateUrl: sortedReleases[0].downloadUrl, 49 | infoUrl: sortedReleases[0].infoUrl, 50 | version: sortedReleases[0].semver, 51 | updateAvailable: 52 | semverCompare(currentVersion, sortedReleases[0].semver) < 0, 53 | }; 54 | }; 55 | 56 | return new Promise((resolve, reject) => { 57 | fetch( 58 | 'https://api.codedead.com/api/v1/applications/47cd7e8f-2744-443c-850e-619df5d5c43f', 59 | ) 60 | .then((res) => { 61 | if (!res.ok) { 62 | throw Error(res.statusText); 63 | } 64 | return res.json(); 65 | }) 66 | .then((data) => resolve(parseUpdate(data))) 67 | .catch((error) => reject(error.toString())); 68 | }); 69 | }; 70 | 71 | export default Updater; 72 | -------------------------------------------------------------------------------- /src/languages/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationName": "端口检查器(Advanced PortChecker)", 3 | "settings": "设置", 4 | "donate": "捐赠", 5 | "about": "关于此工具", 6 | "scan": "IP扫描", 7 | "address": "IP地址", 8 | "startingPort": "起始端口", 9 | "endingPort": "结束端口", 10 | "port": "端口", 11 | "hostName": "主机名", 12 | "portStatus": "端口状态", 13 | "scanDate": "扫描日期", 14 | "error": "错误", 15 | "ok": "成功", 16 | "updateAvailable": "可用更新", 17 | "newVersion": "有版本 {x} 可用,是否下载?", 18 | "information": "信息", 19 | "download": "下载", 20 | "cancel": "取消", 21 | "aboutText": "Advanced PortChecker(端口检查器) 由 DeadLine 创建。\n\n主题:MUI\n许可证:GPLv3\n翻译: 王先生笔记 (https://wxsnote.cn)\n版本:{x}\n\n版权所有 © {year} CodeDead", 22 | "license": "许可证", 23 | "website": "官网", 24 | "autoUpdate": "自动检查更新", 25 | "colorOnDark": "深色主题上的颜色", 26 | "language": "更改语言", 27 | "yes": "是", 28 | "no": "否", 29 | "confirmation": "确认", 30 | "confirmResetSettings": "您确定要重置所有设置吗?", 31 | "default": "默认", 32 | "defaultThemeDescription": "默认主题。", 33 | "lightBlue": "浅蓝色", 34 | "lightBlueDescription": "触感轻盈。", 35 | "red": "红色", 36 | "redDescription": "保持前卫。", 37 | "green": "绿色的", 38 | "greenDescription": "自然是最好的。", 39 | "lightGreen": "浅绿色", 40 | "lightGreenDescription": "草总是更绿。", 41 | "purple": "紫色", 42 | "purpleDescription": "紫晶。", 43 | "deepPurple": "深紫色", 44 | "deepPurpleDescription": "如果紫色还不够。", 45 | "grey": "灰色", 46 | "greyDescription": "别等了。", 47 | "themeStyle": "主题风格", 48 | "light": "明亮", 49 | "dark": "黑暗", 50 | "orange": "橙色", 51 | "orangeThemeDescription": "我们来学荷兰语吧。", 52 | "deepOrange": "深橙色", 53 | "deepOrangeDescription": "如果橙色还不够。", 54 | "amber": "琥珀", 55 | "amberDescription": "不选择性黄色。", 56 | "brown": "棕色", 57 | "brownDescription": "比停电好。", 58 | "pink": "粉色的", 59 | "pinkDescription": "爱的颜色。", 60 | "indigo": "靛蓝色", 61 | "indigoDescription": "它是热带植物吗?", 62 | "cyan": "青色", 63 | "cyanDescription": "介于蓝色和绿色之间。", 64 | "teal": "水鸭", 65 | "tealDescription": "眼睛周围的有色区域。", 66 | "lime": "酸橙", 67 | "limeDescription": "柑橘类水果的颜色。", 68 | "yellow": "黄色", 69 | "yellowDescription": "太阳的颜色。", 70 | "checkForUpdates": "检查更新", 71 | "reset": "重启", 72 | "theme": "主题", 73 | "threads": "运行线程", 74 | "timeout": "超时时间(毫秒)", 75 | "hideClosedPorts": "隐藏未开启的端口", 76 | "hideUnknownPorts": "隐藏未知的端口", 77 | "sort": "按端口号对扫描结果排序", 78 | "clear": "清空", 79 | "open": "打开", 80 | "closed": "关闭", 81 | "exportType": "导出类型", 82 | "export": "导出", 83 | "exportSuccessful": "导出成功", 84 | "runningLatestVersion": "已是最新版本!", 85 | "themeToggleInTopBar": "在顶部栏显示明暗主题切换", 86 | "exportIncludeClosedPorts": "导出时包含关闭的端口", 87 | "exportIncludeUnknownPorts": "导出时包含未知的端口", 88 | "unknown": "未知" 89 | } 90 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src-tauri/src/result.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::time::SystemTime; 4 | 5 | #[derive(Serialize, Deserialize, Clone)] 6 | pub enum PortStatus { 7 | Open, 8 | Closed, 9 | Unknown, 10 | } 11 | 12 | #[derive(Serialize, Deserialize, Clone)] 13 | pub struct ScanResult { 14 | pub address: String, 15 | pub port: u16, 16 | #[serde(rename = "hostName")] 17 | pub host_name: Option, 18 | #[serde(rename = "portStatus")] 19 | pub port_status: PortStatus, 20 | #[serde(rename = "scanDate")] 21 | pub scan_date: Option, 22 | } 23 | 24 | impl ScanResult { 25 | /// Create a new ScanResult 26 | /// 27 | /// # Arguments 28 | /// 29 | /// * `address` - The address that was scanned 30 | /// * `port` - The port that was scanned 31 | /// * `host_name` - The host name of the address that was scanned 32 | /// * `port_status` - The status of the port that was scanned 33 | /// 34 | /// # Returns 35 | /// 36 | /// A new ScanResult 37 | pub fn new(address: &str, port: u16, host_name: &str, port_status: PortStatus) -> ScanResult { 38 | let now = SystemTime::now(); 39 | let now: DateTime = now.into(); 40 | let now = now.to_rfc3339(); 41 | 42 | ScanResult { 43 | address: String::from(address), 44 | port, 45 | host_name: Some(String::from(host_name)), 46 | port_status, 47 | scan_date: Some(now), 48 | } 49 | } 50 | 51 | /// Initialize a ScanResult 52 | /// 53 | /// # Arguments 54 | /// 55 | /// * `address` - The address that was scanned 56 | /// * `port` - The port that was scanned 57 | /// * `host_name` - The host name of the address that was scanned 58 | /// 59 | /// # Returns 60 | /// 61 | /// A new ScanResult 62 | pub fn initialize(address: &str, port: u16) -> ScanResult { 63 | ScanResult { 64 | address: String::from(address), 65 | port, 66 | host_name: None, 67 | port_status: PortStatus::Unknown, 68 | scan_date: None, 69 | } 70 | } 71 | 72 | /// Set the host name,port status and scan date 73 | /// 74 | /// # Arguments 75 | /// 76 | /// * `host_name` - The host name of the address 77 | /// * `port_status` - The status of the port 78 | pub fn set_scan_result(&mut self, host_name: &str, port_status: PortStatus) { 79 | let now = SystemTime::now(); 80 | let now: DateTime = now.into(); 81 | let now = now.to_rfc3339(); 82 | 83 | self.scan_date = Some(now); 84 | self.port_status = port_status; 85 | self.host_name = Some(String::from(host_name)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import importPlugin from 'eslint-plugin-import'; 3 | import react from 'eslint-plugin-react'; 4 | import reactHooks from 'eslint-plugin-react-hooks'; 5 | import reactRefresh from 'eslint-plugin-react-refresh'; 6 | import globals from 'globals'; 7 | 8 | export default [ 9 | importPlugin.flatConfigs.recommended, 10 | { 11 | ignores: [ 12 | 'dist', 13 | '.yarn', 14 | 'dist', 15 | 'node_modules', 16 | 'nginx', 17 | '.idea', 18 | 'src-tauri', 19 | '.github', 20 | 'vite.config.js', 21 | ], 22 | }, 23 | { 24 | files: ['**/*.{js,jsx}'], 25 | languageOptions: { 26 | ecmaVersion: 'latest', 27 | globals: globals.browser, 28 | parserOptions: { 29 | ecmaVersion: 'latest', 30 | ecmaFeatures: { jsx: true }, 31 | sourceType: 'module', 32 | }, 33 | }, 34 | settings: { 35 | react: { version: '19.0' }, 36 | 'import/resolver': { 37 | node: { 38 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 39 | }, 40 | }, 41 | }, 42 | plugins: { 43 | react, 44 | 'react-hooks': reactHooks, 45 | 'react-refresh': reactRefresh, 46 | }, 47 | rules: { 48 | ...js.configs.recommended.rules, 49 | ...react.configs.recommended.rules, 50 | ...react.configs['jsx-runtime'].rules, 51 | ...reactHooks.configs.recommended.rules, 52 | 'no-multi-spaces': ['error', { ignoreEOLComments: false }], 53 | 'no-trailing-spaces': ['error', { skipBlankLines: false }], 54 | 'no-use-before-define': ['error', { functions: true }], 55 | 'react/jsx-no-target-blank': 'off', 56 | 'react-refresh/only-export-components': [ 57 | 'warn', 58 | { allowConstantExport: true }, 59 | ], 60 | 'react/function-component-definition': [ 61 | 2, 62 | { 63 | namedComponents: 'arrow-function', 64 | unnamedComponents: 'arrow-function', 65 | }, 66 | ], 67 | 'react/prop-types': 0, 68 | 'react/jsx-uses-vars': 'error', 69 | 'react/jsx-uses-react': 'error', 70 | semi: [2, 'always'], 71 | quotes: [2, 'single'], 72 | 'import/order': [ 73 | 'error', 74 | { 75 | groups: ['builtin', 'external', 'internal', ['parent', 'sibling']], 76 | pathGroups: [ 77 | { 78 | pattern: 'react', 79 | group: 'external', 80 | position: 'before', 81 | }, 82 | ], 83 | pathGroupsExcludedImportTypes: ['react'], 84 | 'newlines-between': 'never', 85 | alphabetize: { 86 | order: 'asc', 87 | caseInsensitive: true, 88 | }, 89 | }, 90 | ], 91 | }, 92 | }, 93 | ]; 94 | -------------------------------------------------------------------------------- /src/languages/ja_jp.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationName": "Advanced PortChecker", 3 | "settings": "設定", 4 | "donate": "寄付", 5 | "about": "について", 6 | "scan": "スキャン", 7 | "address": "アドレス", 8 | "startingPort": "開始ポート", 9 | "endingPort": "終了ポート", 10 | "port": "ポート", 11 | "hostName": "ホスト名", 12 | "portStatus": "ポートの状態", 13 | "scanDate": "スキャン日時", 14 | "error": "エラー", 15 | "ok": "わかった", 16 | "updateAvailable": "更新が利用可能", 17 | "newVersion": "バージョン {x} が利用可能です。ダウンロードしますか?", 18 | "information": "情報", 19 | "download": "ダウンロード", 20 | "cancel": "キャンセル", 21 | "aboutText": "Advanced PortChecker は DeadLine によって作成されました。\n\nテーマ : MUI\nライセンス : GPLv3\n翻訳者:coolvitto\nバージョン : {x}\n\nCopyright 息 {year} CodeDead", 22 | "license": "ライセンス", 23 | "website": "ウェブサイト", 24 | "autoUpdate": "更新の自動確認", 25 | "colorOnDark": "ダークテーマの色", 26 | "language": "言語", 27 | "yes": "はい", 28 | "no": "いいえ", 29 | "confirmation": "確認", 30 | "confirmResetSettings": "すべての設定をリセットしますか?", 31 | "default": "既定", 32 | "defaultThemeDescription": "既定のテーマ。", 33 | "lightBlue": "明るい青", 34 | "lightBlueDescription": "軽いタッチの青", 35 | "red": "赤", 36 | "redDescription": "エッジの効いた赤", 37 | "green": "緑", 38 | "greenDescription": "自然の極致", 39 | "lightGreen": "明るい緑", 40 | "lightGreenDescription": "青々としている草。", 41 | "purple": "紫", 42 | "purpleDescription": "アメジスト。", 43 | "deepPurple": "濃い紫", 44 | "deepPurpleDescription": "紫よりも濃い。", 45 | "grey": "灰", 46 | "greyDescription": "待たないで。", 47 | "themeStyle": "テーマのスタイル", 48 | "light": "ライト", 49 | "dark": "ダーク", 50 | "orange": "オレンジ", 51 | "orangeThemeDescription": "オランダ語を学びましょう。", 52 | "deepOrange": "濃いオレンジ", 53 | "deepOrangeDescription": "オレンジよりも濃い。", 54 | "amber": "アンバー", 55 | "amberDescription": "選択的な黄色ではありません。", 56 | "brown": "茶色", 57 | "brownDescription": "ブラウンアウトよりはマシ。", 58 | "pink": "ピンク", 59 | "pinkDescription": "愛の色。", 60 | "indigo": "インディゴ", 61 | "indigoDescription": "それは熱帯植物ですか?", 62 | "cyan": "シアン", 63 | "cyanDescription": "青と緑の中間。", 64 | "teal": "ティール", 65 | "tealDescription": "目の周りの色付きの領域。", 66 | "lime": "ライム", 67 | "limeDescription": "柑橘類の色。", 68 | "yellow": "黄", 69 | "yellowDescription": "太陽の色。", 70 | "checkForUpdates": "更新の確認", 71 | "reset": "リセット", 72 | "theme": "テーマ", 73 | "threads": "スレッド", 74 | "timeout": "タイムアウト (ミリ秒)", 75 | "hideClosedPorts": "閉じたポートを非表示", 76 | "hideUnknownPorts": "不明なポートを非表示", 77 | "sort": "ポート番号で結果を並べ替え", 78 | "clear": "消去", 79 | "open": "開く", 80 | "closed": "閉じる", 81 | "exportType": "エクスポート形式", 82 | "export": "エクスポート", 83 | "exportSuccessful": "エクスポート成功", 84 | "runningLatestVersion": "最新バージョンを実行しています。", 85 | "themeToggleInTopBar": "トップバーでテーマを切り替える", 86 | "exportIncludeClosedPorts": "エクスポートに閉じたポートを含める", 87 | "exportIncludeUnknownPorts": "エクスポートに不明なポートを含める", 88 | "unknown": "不明" 89 | } 90 | -------------------------------------------------------------------------------- /src/contexts/MainContextProvider/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from 'react'; 2 | import enUs from '../../languages/en_us.json'; 3 | import esEs from '../../languages/es_es.json'; 4 | import frFr from '../../languages/fr_fr.json'; 5 | import itIt from '../../languages/it_it.json'; 6 | import jaJp from '../../languages/ja_jp.json'; 7 | import nlNl from '../../languages/nl_nl.json'; 8 | import zhCn from '../../languages/zh_cn.json'; 9 | import MainReducer from '../../reducers/MainReducer'; 10 | 11 | const languageIndex = localStorage.languageIndex 12 | ? parseFloat(localStorage.languageIndex) 13 | : 0; 14 | const themeIndex = localStorage.themeIndex 15 | ? parseFloat(localStorage.themeIndex) 16 | : 0; 17 | const themeType = localStorage.themeType ? localStorage.themeType : 'light'; 18 | const autoUpdate = localStorage.autoUpdate 19 | ? localStorage.autoUpdate === 'true' 20 | : true; 21 | const colorOnDark = localStorage.colorOnDark 22 | ? localStorage.colorOnDark === 'true' 23 | : false; 24 | 25 | const threads = localStorage.threads ? parseFloat(localStorage.threads) : 1; 26 | const timeout = localStorage.timeout ? parseFloat(localStorage.timeout) : 250; 27 | const noClosed = localStorage.noClosed 28 | ? localStorage.noClosed === 'true' 29 | : false; 30 | const noUnknown = localStorage.noUnknown 31 | ? localStorage.noUnknown === 'true' 32 | : false; 33 | const exportNoClosed = localStorage.exportNoClosed 34 | ? localStorage.exportNoClosed === 'true' 35 | : true; 36 | const exportNoUnknown = localStorage.exportNoUnknown 37 | ? localStorage.exportNoUnknown === 'true' 38 | : true; 39 | const sort = localStorage.sort ? localStorage.sort === 'true' : true; 40 | const themeToggle = localStorage.themeToggle 41 | ? localStorage.themeToggle === 'true' 42 | : true; 43 | 44 | const initState = { 45 | autoUpdate, 46 | languageIndex, 47 | languages: [enUs, esEs, frFr, itIt, jaJp, nlNl, zhCn], 48 | themeIndex, 49 | themeType, 50 | pageIndex: 0, 51 | update: null, 52 | checkedForUpdates: false, 53 | loading: false, 54 | colorOnDark, 55 | error: null, 56 | addresses: [''], 57 | startPort: 0, 58 | endPort: 65535, 59 | isScanning: false, 60 | isCancelling: false, 61 | threads, 62 | timeout, 63 | noClosed, 64 | noUnknown, 65 | exportNoClosed, 66 | exportNoUnknown, 67 | sort, 68 | scanResults: null, 69 | themeToggle, 70 | }; 71 | 72 | // eslint-disable-next-line react-refresh/only-export-components 73 | export const MainContext = createContext(initState); 74 | 75 | const MainContextProvider = ({ children }) => { 76 | const [state, dispatch] = useReducer(MainReducer, initState); 77 | 78 | return ( 79 | 80 | {children} 81 | 82 | ); 83 | }; 84 | 85 | export default MainContextProvider; 86 | -------------------------------------------------------------------------------- /src/languages/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationName": "Advanced PortChecker", 3 | "settings": "Settings", 4 | "donate": "Donate", 5 | "about": "About", 6 | "scan": "Scan", 7 | "address": "Address", 8 | "startingPort": "Starting port", 9 | "endingPort": "Ending port", 10 | "port": "Port", 11 | "hostName": "Hostname", 12 | "portStatus": "Port status", 13 | "scanDate": "Scan date", 14 | "error": "Error", 15 | "ok": "OK", 16 | "updateAvailable": "Update available", 17 | "newVersion": "Version {x} is available. Do you want to download it?", 18 | "information": "Information", 19 | "download": "Download", 20 | "cancel": "Cancel", 21 | "aboutText": "Advanced PortChecker was created by DeadLine.\n\nTheme: MUI\nLicense: GPLv3\nVersion: {x}\n\nCopyright © {year} CodeDead", 22 | "license": "License", 23 | "website": "Website", 24 | "autoUpdate": "Automatically check for updates", 25 | "colorOnDark": "Color on dark theme", 26 | "language": "Language", 27 | "yes": "Yes", 28 | "no": "No", 29 | "confirmation": "Confirmation", 30 | "confirmResetSettings": "Are you sure you want to reset all settings?", 31 | "default": "Default", 32 | "defaultThemeDescription": "The default theme.", 33 | "lightBlue": "Light blue", 34 | "lightBlueDescription": "Light to the touch.", 35 | "red": "Red", 36 | "redDescription": "Keeping it edgy.", 37 | "green": "Green", 38 | "greenDescription": "Nature's finest.", 39 | "lightGreen": "Light green", 40 | "lightGreenDescription": "The grass is always greener.", 41 | "purple": "Purple", 42 | "purpleDescription": "Amethyst.", 43 | "deepPurple": "Deep purple", 44 | "deepPurpleDescription": "In case purple wasn't enough.", 45 | "grey": "Grey", 46 | "greyDescription": "Don't wait up.", 47 | "themeStyle": "Theme style", 48 | "light": "Light", 49 | "dark": "Dark", 50 | "orange": "Orange", 51 | "orangeThemeDescription": "Let's get Dutch.", 52 | "deepOrange": "Deep orange", 53 | "deepOrangeDescription": "In case orange wasn't enough.", 54 | "amber": "Amber", 55 | "amberDescription": "Not selective yellow.", 56 | "brown": "Brown", 57 | "brownDescription": "Better than a brownout.", 58 | "pink": "Pink", 59 | "pinkDescription": "The color of love.", 60 | "indigo": "Indigo", 61 | "indigoDescription": "Is it a tropical plant?", 62 | "cyan": "Cyan", 63 | "cyanDescription": "Halfway between blue and green.", 64 | "teal": "Teal", 65 | "tealDescription": "The colored area around the eye.", 66 | "lime": "Lime", 67 | "limeDescription": "The color of the citrus fruit.", 68 | "yellow": "Yellow", 69 | "yellowDescription": "The color of the sun.", 70 | "checkForUpdates": "Check for updates", 71 | "reset": "Reset", 72 | "theme": "Theme", 73 | "threads": "Threads", 74 | "timeout": "Timeout (milliseconds)", 75 | "hideClosedPorts": "Hide closed ports", 76 | "hideUnknownPorts": "Hide unknown ports", 77 | "sort": "Sort results by port number", 78 | "clear": "Clear", 79 | "open": "Open", 80 | "closed": "Closed", 81 | "exportType": "Export type", 82 | "export": "Export", 83 | "exportSuccessful": "Export successful", 84 | "runningLatestVersion": "You are running the latest version.", 85 | "themeToggleInTopBar": "Toggle theme in top bar", 86 | "exportIncludeClosedPorts": "Include closed ports in exports", 87 | "exportIncludeUnknownPorts": "Include unknown ports in exports", 88 | "unknown": "Unknown" 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced PortChecker 2 | 3 | ![Advanced PortChecker](https://i.imgur.com/vdt1sXZ.png) 4 | 5 | ![GitHub](https://img.shields.io/badge/language-JavaScript+Rust-green) 6 | ![GitHub](https://img.shields.io/github/license/CodeDead/Advanced-PortChecker) 7 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/CodeDead/Advanced-PortChecker) 8 | [![Test](https://github.com/CodeDead/Advanced-PortChecker/actions/workflows/test.yml/badge.svg)](https://github.com/CodeDead/Advanced-PortChecker/actions/workflows/test.yml) 9 | [![Release](https://github.com/CodeDead/Advanced-PortChecker/actions/workflows/release.yml/badge.svg)](https://github.com/CodeDead/Advanced-PortChecker/actions/workflows/release.yml) 10 | 11 | Advanced PortChecker is a free and open-source application that can help you check if ports are open or closed on a certain host. You can check multiple ports at once and export the results in plain text, CSV or JSON format! 12 | 13 | ## Building 14 | 15 | Advanced PortChecker uses tauri to build the desktop application. You can find more information about Tauri [here](https://tauri.app/v1/guides/). 16 | 17 | For more information about building using `vite`, please read the `Vite` documentation [here](https://vitejs.dev/guide/build.html). 18 | 19 | ### Development 20 | 21 | You can start a development preview by running the `yarn tdev` command: 22 | ```shell 23 | yarn tdev 24 | ``` 25 | 26 | ### Windows 27 | 28 | #### Installer 29 | 30 | You can create an executable installer by running the `yarn tbuild` command on a Windows host: 31 | ```shell 32 | yarn tbuild 33 | ``` 34 | 35 | ### Linux 36 | 37 | #### deb 38 | 39 | You can create a .deb file, by running the `yarn tbuild` command on a Linux host: 40 | ```shell 41 | yarn tbuild 42 | ``` 43 | 44 | #### AppImage 45 | 46 | You can create an [AppImage](https://appimage.github.io/) by executing the `yarn tbuild` command on a Linux host: 47 | ```shell 48 | yarn tbuild 49 | ``` 50 | 51 | ### macOS 52 | 53 | #### dmg 54 | 55 | You can create a macOS build by running the `yarn tbuild` command on a macOS host: 56 | ```shell 57 | yarn tbuild 58 | ``` 59 | 60 | #### Archive 61 | 62 | You can create a macOS build by running the `yarn tbuild` command on a macOS host: 63 | ```shell 64 | yarn tbuild 65 | ``` 66 | 67 | ## Credits 68 | 69 | ### Tauri 70 | 71 | This project uses [Tauri](https://tauri.app/) to create a cross-platform application. 72 | 73 | ### ReactJS 74 | 75 | This project uses [React](https://reactjs.org/) to create the user interface. 76 | 77 | ### Theme 78 | 79 | The theme used in this application is [MUI](https://mui.com/). 80 | 81 | ### Images 82 | 83 | The application icon (and derivatives) are provided by [RemixIcon](https://remixicon.com/). 84 | All other images were provided by [MUI](https://mui.com/material-ui/material-icons/). 85 | 86 | ### Translations 87 | 88 | - Chinese (Simplified) - [王先生笔记](https://wxsnote.cn) 89 | - Italian - [bovirus](https://github.com/bovirus) 90 | - Japanese - [coolvitto](https://github.com/coolvitto) 91 | 92 | ## About 93 | 94 | This library is maintained by CodeDead. You can find more about us using the following links: 95 | * [Website](https://codedead.com) 96 | * [Bluesky](https://bsky.app/profile/codedead.com) 97 | * [Facebook](https://facebook.com/deadlinecodedead) 98 | 99 | Copyright © 2025 CodeDead 100 | -------------------------------------------------------------------------------- /src/languages/nl_nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationName": "Advanced PortChecker", 3 | "settings": "Instellingen", 4 | "donate": "Doneren", 5 | "about": "Over", 6 | "scan": "Scannen", 7 | "address": "Adres", 8 | "startingPort": "Start poort", 9 | "endingPort": "Eind poort", 10 | "port": "Poort", 11 | "hostName": "Hostnaam", 12 | "portStatus": "Poortstatus", 13 | "scanDate": "Scandatum", 14 | "error": "Fout", 15 | "ok": "OK", 16 | "updateAvailable": "Update beschikbaar", 17 | "newVersion": "Versie {x} is nu beschikbaar. Wenst u deze versie te downloaden?", 18 | "information": "Informatie", 19 | "download": "Download", 20 | "cancel": "Annuleren", 21 | "aboutText": "Advanced PortChecker werd gemaakt door DeadLine.\n\nThema: MUI\nLicentie: GPLv3\nVersie: {x}\n\nCopyright © {year} CodeDead", 22 | "license": "Licentie", 23 | "website": "Website", 24 | "autoUpdate": "Automatisch updaten", 25 | "colorOnDark": "Kleur op donkere achtergrond", 26 | "language": "Taal", 27 | "yes": "Ja", 28 | "no": "Nee", 29 | "confirmation": "Bevestiging", 30 | "confirmResetSettings": "Bent u zeker dat u de instellingen wenst te resetten?", 31 | "default": "Standaard", 32 | "defaultThemeDescription": "Het standaard thema.", 33 | "lightBlue": "Lichtblauw", 34 | "lightBlueDescription": "Licht om aan te raken.", 35 | "red": "Rood", 36 | "redDescription": "Het edgy houden.", 37 | "green": "Groen", 38 | "greenDescription": "Het beste van de natuur.", 39 | "lightGreen": "Lichtgroen", 40 | "lightGreenDescription": "Het gras is altijd groener.", 41 | "purple": "Paars", 42 | "purpleDescription": "De kleur van royalty.", 43 | "deepPurple": "Donkerpaars", 44 | "deepPurpleDescription": "Indien paars niet genoeg is.", 45 | "grey": "Grijs", 46 | "greyDescription": "Blijf niet te lang wakker.", 47 | "themeStyle": "Thema stijl", 48 | "light": "Licht", 49 | "dark": "Donker", 50 | "orange": "Oranje", 51 | "orangeThemeDescription": "Op z'n Nederlands.", 52 | "deepOrange": "Donkeroranje", 53 | "deepOrangeDescription": "De kleur van de zonsondergang.", 54 | "amber": "Amber", 55 | "amberDescription": "Geen selectief geel.", 56 | "brown": "Bruin", 57 | "brownDescription": "De kleur van de aarde.", 58 | "pink": "Roze", 59 | "pinkDescription": "De kleur van de liefde.", 60 | "indigo": "Indigo", 61 | "indigoDescription": "De kleur van de nacht.", 62 | "cyan": "Cyaan", 63 | "cyanDescription": "De kleur van de oceaan.", 64 | "teal": "Teal", 65 | "tealDescription": "De kleur van de tropen.", 66 | "lime": "Lime", 67 | "limeDescription": "De kleur van de limoen.", 68 | "yellow": "Geel", 69 | "yellowDescription": "De kleur van de zon.", 70 | "checkForUpdates": "Controleer op updates", 71 | "reset": "Reset", 72 | "theme": "Thema", 73 | "threads": "Threads", 74 | "timeout": "Timeout (milliseconden)", 75 | "hideClosedPorts": "Verberg gesloten poorten", 76 | "hideUnknownPorts": "Verberg onbekende poorten", 77 | "sort": "Sorteer volgens poortnummer", 78 | "clear": "Wissen", 79 | "open": "Open", 80 | "closed": "Gesloten", 81 | "exportType": "Export type", 82 | "export": "Exporteren", 83 | "exportSuccessful": "Exporteren succesvol", 84 | "runningLatestVersion": "U gebruikt de laatste versie.", 85 | "themeToggleInTopBar": "Thema toggle in de bovenste balk", 86 | "exportIncludeClosedPorts": "Gesloten poorten opnemen in export", 87 | "exportIncludeUnknownPorts": "Onbekende poorten opnemen in export", 88 | "unknown": "Onbekend" 89 | } 90 | -------------------------------------------------------------------------------- /src/languages/es_es.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationName": "Advanced PortChecker", 3 | "settings": "Configuración", 4 | "donate": "Donar", 5 | "about": "Acerca de", 6 | "scan": "Escanear", 7 | "address": "Alocución", 8 | "startingPort": "Puerto de salida", 9 | "endingPort": "Puerto final", 10 | "port": "Puerto", 11 | "hostName": "Nombre de host", 12 | "portStatus": "Estado del puerto", 13 | "scanDate": "Fecha de escaneo", 14 | "error": "Error", 15 | "ok": "OK", 16 | "updateAvailable": "Actualización disponible", 17 | "newVersion": "La versión {x} está disponible. ¿Quieres descargarlo?", 18 | "information": "Información", 19 | "download": "Descargar", 20 | "cancel": "Cancelar", 21 | "aboutText": "Advanced PortChecker fue creado por DeadLine.\n\nTema: MUI\nLicencia: GPLv3\nVersión: {x}\n\nCopyright © {year} CodeDead", 22 | "license": "Licencia", 23 | "website": "Sitio web", 24 | "autoUpdate": "Buscar actualizaciones automáticamente", 25 | "colorOnDark": "Color sobre tema oscuro", 26 | "language": "Idioma", 27 | "yes": "Sí", 28 | "no": "No", 29 | "confirmation": "Confirmación", 30 | "confirmResetSettings": "¿Estás seguro de que quieres restablecer todos los ajustes?", 31 | "default": "Por defecto", 32 | "defaultThemeDescription": "El tema predeterminado.", 33 | "lightBlue": "Azul claro", 34 | "lightBlueDescription": "Ligero al tacto.", 35 | "red": "Rojo", 36 | "redDescription": "Manteniéndolo nervioso.", 37 | "green": "Verde", 38 | "greenDescription": "El color de la hierba.", 39 | "lightGreen": "Verde claro", 40 | "lightGreenDescription": "Ligero al tacto.", 41 | "purple": "Púrpura", 42 | "purpleDescription": "El color de la realeza.", 43 | "deepPurple": "Púrpura profundo", 44 | "deepPurpleDescription": "Más profundo que el púrpura.", 45 | "grey": "Gris", 46 | "greyDescription": "El color de la ceniza.", 47 | "themeStyle": "Estilo del tema", 48 | "light": "Ligero", 49 | "dark": "Oscuro", 50 | "orange": "Naranja", 51 | "orangeThemeDescription": "El color de la fruta.", 52 | "deepOrange": "Naranja profundo", 53 | "deepOrangeDescription": "Más profundo que el naranja.", 54 | "amber": "Ámbar", 55 | "amberDescription": "El color de la resina.", 56 | "brown": "Marrón", 57 | "brownDescription": "El color de la madera.", 58 | "pink": "Rosa", 59 | "pinkDescription": "El color de la flor.", 60 | "indigo": "Índigo", 61 | "indigoDescription": "¿Es una planta tropical?", 62 | "cyan": "Cian", 63 | "cyanDescription": "El color del cielo.", 64 | "teal": "Teal", 65 | "tealDescription": "El área coloreada alrededor del ojo.", 66 | "lime": "Cal", 67 | "limeDescription": "El color de los cítricos.", 68 | "yellow": "Amarillo", 69 | "yellowDescription": "El color del sol.", 70 | "checkForUpdates": "Buscar actualizaciones", 71 | "reset": "Restablecer", 72 | "theme": "Tema", 73 | "threads": "Hilos", 74 | "timeout": "Tiempo de espera (milisegundos)", 75 | "hideClosedPorts": "Ocultar puertos cerrados", 76 | "hideUnknownPorts": "Ocultar puertos desconocidos", 77 | "sort": "Ordenar resultados por número de puerto", 78 | "clear": "Limpiar todo", 79 | "open": "Abierto", 80 | "closed": "Cerrado", 81 | "exportType": "Tipo de exportación", 82 | "export": "Exportar", 83 | "exportSuccessful": "Exportación exitosa", 84 | "runningLatestVersion": "Estás ejecutando la última versión.", 85 | "themeToggleInTopBar": "Alternar tema en la barra superior", 86 | "exportIncludeClosedPorts": "Incluir puertos cerrados en las exportaciones", 87 | "exportIncludeUnknownPorts": "Incluir puertos desconocidos en las exportaciones", 88 | "unknown": "Desconocido" 89 | } 90 | -------------------------------------------------------------------------------- /src/languages/it_it.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationName": "Advanced PortChecker", 3 | "settings": "Impostazioni", 4 | "donate": "Dona", 5 | "about": "Info programma", 6 | "scan": "Scansiona", 7 | "address": "Indirizzo", 8 | "startingPort": "Porta iniziale", 9 | "endingPort": "Porta finale", 10 | "port": "Porta", 11 | "hostName": "Nome host", 12 | "portStatus": "Stato porta", 13 | "scanDate": "Data scansione", 14 | "error": "Errore", 15 | "ok": "OK", 16 | "updateAvailable": "Disponibile aggiornamento programma", 17 | "newVersion": "È disponibile la versione {x}.\nVuoi aggiornare il programma?", 18 | "information": "Informazioni", 19 | "download": "Download", 20 | "cancel": "Annulla", 21 | "aboutText": "Advanced PortChecker è stato creato da DeadLine.\n\nTema: MUI\nLicenza: GPLv3\nTraduzioni: bovirus\nVersione: {x}\n\nCopyright © {year} CodeDead", 22 | "license": "Licenza", 23 | "website": "Sito web", 24 | "autoUpdate": "Controlla automaticamente disponibilità aggiornamenti", 25 | "colorOnDark": "Colore per il tema scuro", 26 | "language": "Lingua", 27 | "yes": "Sì", 28 | "no": "No", 29 | "confirmation": "Conferme", 30 | "confirmResetSettings": "Vuoi ripristinare tutte le impostazioni?", 31 | "default": "Predefinito", 32 | "defaultThemeDescription": "Il tema predefinito.", 33 | "lightBlue": "Blu chiaro", 34 | "lightBlueDescription": "Leggero al tatto.", 35 | "red": "Rosso", 36 | "redDescription": "Mantieni tagliente.", 37 | "green": "Verde", 38 | "greenDescription": "La natura è la migliore.", 39 | "lightGreen": "Verde chiaro", 40 | "lightGreenDescription": "L'erba è sempre più verde.", 41 | "purple": "Viola", 42 | "purpleDescription": "Ametista.", 43 | "deepPurple": "Viola intenso", 44 | "deepPurpleDescription": "Nel caso il viola non bastasse.", 45 | "grey": "Grigio", 46 | "greyDescription": "Non aspettare alzato.", 47 | "themeStyle": "Stile tema", 48 | "light": "Chiaro", 49 | "dark": "Scuro", 50 | "orange": "Arancione", 51 | "orangeThemeDescription": "Prendiamo l'olandese.", 52 | "deepOrange": "Arancione intenso", 53 | "deepOrangeDescription": "Nel caso in cui l'arancione non bastasse.", 54 | "amber": "Ambra", 55 | "amberDescription": "Giallo non selettivo.", 56 | "brown": "Marino", 57 | "brownDescription": "Meglio di un brownout.", 58 | "pink": "Rosa", 59 | "pinkDescription": "Il colore dell'amore.", 60 | "indigo": "Indaco", 61 | "indigoDescription": "È una pianta tropicale?", 62 | "cyan": "Ciano", 63 | "cyanDescription": "A metà tra il blu e il verde.", 64 | "teal": "Verde acqua", 65 | "tealDescription": "L'area colorata intorno all'occhio.", 66 | "lime": "Lime", 67 | "limeDescription": "Il colore degli agrumi.", 68 | "yellow": "Giallo", 69 | "yellowDescription": "Il colore del sole.", 70 | "checkForUpdates": "Controlla aggiornamenti", 71 | "reset": "Ripristina", 72 | "theme": "Tema", 73 | "threads": "Thread", 74 | "timeout": "Timeout (millisecondi)", 75 | "hideClosedPorts": "Nascondi porte chiuse", 76 | "hideUnknownPorts": "Nascond porta sconosciuta", 77 | "sort": "Ordina risultati per numero di porta", 78 | "clear": "Azzera", 79 | "open": "Apri", 80 | "closed": "Chiusa", 81 | "exportType": "Tipo esportazione", 82 | "export": "Export", 83 | "exportSuccessful": "Esportazione completata", 84 | "runningLatestVersion": "La versione in uso è aggiornata.", 85 | "themeToggleInTopBar": "Attiva/disattiva tema nella barra superiore", 86 | "exportIncludeClosedPorts": "Includi porte chiuse nelle esportazioni", 87 | "exportIncludeUnknownPorts": "Includi porte sconosciute nelle esportazioni", 88 | "unknown": "Sconosciuto" 89 | } 90 | -------------------------------------------------------------------------------- /src/languages/fr_fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationName": "Advanced PortChecker", 3 | "settings": "Réglages", 4 | "donate": "Faire un don", 5 | "about": "À propos", 6 | "scan": "Scanner", 7 | "address": "Adresse", 8 | "startingPort": "Démarrage du port TCP", 9 | "endingPort": "Fin du port TCP", 10 | "port": "Port", 11 | "hostName": "Nom d'hôte", 12 | "portStatus": "Statut du port", 13 | "scanDate": "Date d'analyse", 14 | "error": "Erreur", 15 | "ok": "D'ACCORD", 16 | "updateAvailable": "Mise à jour disponible", 17 | "newVersion": "La version {x} est disponible. Voulez-vous le télécharger ?", 18 | "information": "Information", 19 | "download": "Télécharger", 20 | "cancel": "Annuler", 21 | "aboutText": "Advanced PortChecker a été créé par DeadLine.\n\nThème : MUI\nLicence : GPLv3\nVersion : {x}\n\nCopyright © {year} CodeDead", 22 | "license": "Licence", 23 | "website": "Site web", 24 | "autoUpdate": "Rechercher automatiquement les mises à jour", 25 | "colorOnDark": "Couleur sur thème sombre", 26 | "language": "Langue", 27 | "yes": "Oui", 28 | "no": "Non", 29 | "confirmation": "Confirmation", 30 | "confirmResetSettings": "Êtes-vous sûr de vouloir réinitialiser tous les paramètres ?", 31 | "default": "Défaut", 32 | "defaultThemeDescription": "Le thème par défaut.", 33 | "lightBlue": "Bleu clair", 34 | "lightBlueDescription": "Léger au toucher.", 35 | "red": "Rouge", 36 | "redDescription": "Le garder nerveux.", 37 | "green": "Vert", 38 | "greenDescription": "Le meilleur de la nature.", 39 | "lightGreen": "Vert clair", 40 | "lightGreenDescription": "L'herbe est toujours plus verte.", 41 | "purple": "Violet", 42 | "purpleDescription": "Améthyste.", 43 | "deepPurple": "Violet foncé", 44 | "deepPurpleDescription": "Au cas où le violet ne suffirait pas.", 45 | "grey": "Gris", 46 | "greyDescription": "N'attendez pas.", 47 | "themeStyle": "Style de thème", 48 | "light": "Clair", 49 | "dark": "Sombre", 50 | "orange": "Orange", 51 | "orangeThemeDescription": "La couleur de la citrouille.", 52 | "deepOrange": "Orange foncé", 53 | "deepOrangeDescription": "La couleur de la mandarine.", 54 | "amber": "Ambre", 55 | "amberDescription": "La couleur de l'ambre.", 56 | "brown": "Brun", 57 | "brownDescription": "La couleur du bois.", 58 | "pink": "Rose", 59 | "pinkDescription": "La couleur de la fleur.", 60 | "indigo": "Indigo", 61 | "indigoDescription": "Est-ce une plante tropicale ?", 62 | "cyan": "Cyan", 63 | "cyanDescription": "La couleur de l'eau.", 64 | "teal": "Sarcelle", 65 | "tealDescription": "La zone colorée autour de l’œil.", 66 | "lime": "Citron vert", 67 | "limeDescription": "La couleur du citron vert.", 68 | "yellow": "Jaune", 69 | "yellowDescription": "La couleur du soleil.", 70 | "checkForUpdates": "Vérifier les mises à jour", 71 | "reset": "Réinitialiser", 72 | "theme": "Thème", 73 | "threads": "Threads", 74 | "timeout": "Délai d'attente (en millisecondes)", 75 | "hideClosedPorts": "Masquer les ports fermés", 76 | "hideUnknownPorts": "Masquer les ports inconnus", 77 | "sort": "Trier les résultats par numéro de port", 78 | "clear": "Effacer", 79 | "open": "Ouvert", 80 | "closed": "Fermé", 81 | "exportType": "Type d'exportation", 82 | "export": "Exporter", 83 | "exportSuccessful": "Exportation réussie", 84 | "runningLatestVersion": "Vous utilisez la dernière version.", 85 | "themeToggleInTopBar": "Basculer le thème dans la barre supérieure", 86 | "exportIncludeClosedPorts": "Inclure les ports fermés dans les exportations", 87 | "exportIncludeUnknownPorts": "Inclure les ports inconnus dans les exportations", 88 | "unknown": "Inconnu" 89 | } 90 | -------------------------------------------------------------------------------- /src/reducers/MainReducer/Actions/index.js: -------------------------------------------------------------------------------- 1 | import { invoke } from '@tauri-apps/api/core'; 2 | import { 3 | RESET_STATE, 4 | SET_ADDRESSES, 5 | SET_AUTO_UPDATE, 6 | SET_CHECKED_FOR_UPDATES, 7 | SET_COLOR_ON_DARK, 8 | SET_END_PORT, 9 | SET_ERROR, 10 | SET_EXPORT_NO_CLOSED, 11 | SET_EXPORT_NO_UNKNOWN, 12 | SET_IS_CANCELLING, 13 | SET_IS_SCANNING, 14 | SET_LANGUAGE_INDEX, 15 | SET_LOADING, 16 | SET_NO_CLOSED, 17 | SET_NO_UNKNOWN, 18 | SET_PAGE_INDEX, 19 | SET_SCAN_RESULTS, 20 | SET_SORT, 21 | SET_START_PORT, 22 | SET_THEME_INDEX, 23 | SET_THEME_TOGGLE, 24 | SET_THEME_TYPE, 25 | SET_THREADS, 26 | SET_TIMEOUT, 27 | SET_UPDATE, 28 | } from './actionTypes'; 29 | 30 | export const setLanguageIndex = (index) => ({ 31 | type: SET_LANGUAGE_INDEX, 32 | payload: index, 33 | }); 34 | 35 | export const setThemeIndex = (index) => ({ 36 | type: SET_THEME_INDEX, 37 | payload: index, 38 | }); 39 | 40 | export const setThemeType = (type) => ({ 41 | type: SET_THEME_TYPE, 42 | payload: type, 43 | }); 44 | 45 | export const resetState = () => ({ 46 | type: RESET_STATE, 47 | }); 48 | 49 | export const setPageIndex = (index) => ({ 50 | type: SET_PAGE_INDEX, 51 | payload: index, 52 | }); 53 | 54 | export const setAutoUpdate = (value) => ({ 55 | type: SET_AUTO_UPDATE, 56 | payload: value, 57 | }); 58 | 59 | export const openWebSite = (website) => { 60 | invoke('open_website', { website }).catch((e) => { 61 | console.error(e); 62 | }); 63 | }; 64 | 65 | export const scanAddresses = ( 66 | addresses, 67 | startPort, 68 | endPort, 69 | timeout, 70 | threads, 71 | sort, 72 | ) => { 73 | const cmd = { 74 | addresses, 75 | startPort: parseInt(startPort, 10), 76 | endPort: parseInt(endPort, 10), 77 | timeout: parseFloat(timeout), 78 | threads: parseInt(threads, 10), 79 | sort, 80 | }; 81 | 82 | return invoke('scan_port_range', cmd); 83 | }; 84 | 85 | export const setUpdate = (update) => ({ 86 | type: SET_UPDATE, 87 | payload: update, 88 | }); 89 | 90 | export const setError = (error) => ({ 91 | type: SET_ERROR, 92 | payload: error, 93 | }); 94 | 95 | export const setLoading = (value) => ({ 96 | type: SET_LOADING, 97 | payload: value, 98 | }); 99 | 100 | export const setCheckedForUpdates = (value) => ({ 101 | type: SET_CHECKED_FOR_UPDATES, 102 | payload: value, 103 | }); 104 | 105 | export const setColorOnDark = (value) => ({ 106 | type: SET_COLOR_ON_DARK, 107 | payload: value, 108 | }); 109 | 110 | export const setAddresses = (addresses) => ({ 111 | type: SET_ADDRESSES, 112 | payload: addresses, 113 | }); 114 | 115 | export const setStartPort = (port) => ({ 116 | type: SET_START_PORT, 117 | payload: port, 118 | }); 119 | 120 | export const setEndPort = (port) => ({ 121 | type: SET_END_PORT, 122 | payload: port, 123 | }); 124 | 125 | export const setIsScanning = (value) => ({ 126 | type: SET_IS_SCANNING, 127 | payload: value, 128 | }); 129 | 130 | export const setThreads = (value) => ({ 131 | type: SET_THREADS, 132 | payload: value, 133 | }); 134 | 135 | export const setTimeout = (value) => ({ 136 | type: SET_TIMEOUT, 137 | payload: value, 138 | }); 139 | 140 | export const setNoClosed = (value) => ({ 141 | type: SET_NO_CLOSED, 142 | payload: value, 143 | }); 144 | 145 | export const setSort = (value) => ({ 146 | type: SET_SORT, 147 | payload: value, 148 | }); 149 | 150 | export const setScanResults = (value) => ({ 151 | type: SET_SCAN_RESULTS, 152 | payload: value, 153 | }); 154 | 155 | export const cancelScan = () => invoke('cancel_scan'); 156 | 157 | export const getNumberOfThreads = () => invoke('get_number_of_threads'); 158 | 159 | export const setThemeToggle = (value) => ({ 160 | type: SET_THEME_TOGGLE, 161 | payload: value, 162 | }); 163 | 164 | export const setExportNoClosed = (value) => ({ 165 | type: SET_EXPORT_NO_CLOSED, 166 | payload: value, 167 | }); 168 | 169 | export const setIsCancelling = (value) => ({ 170 | type: SET_IS_CANCELLING, 171 | payload: value, 172 | }); 173 | 174 | export const setNoUnknown = (value) => ({ 175 | type: SET_NO_UNKNOWN, 176 | payload: value, 177 | }); 178 | 179 | export const setExportNoUnknown = (value) => ({ 180 | type: SET_EXPORT_NO_UNKNOWN, 181 | payload: value, 182 | }); 183 | -------------------------------------------------------------------------------- /src/components/Layout/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useContext, useEffect } from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import CssBaseline from '@mui/material/CssBaseline'; 4 | import { createTheme, ThemeProvider } from '@mui/material/styles'; 5 | import { getVersion } from '@tauri-apps/api/app'; 6 | import { platform, arch } from '@tauri-apps/plugin-os'; 7 | import { Outlet } from 'react-router-dom'; 8 | import { MainContext } from '../../contexts/MainContextProvider'; 9 | import { 10 | getNumberOfThreads, 11 | openWebSite, 12 | setCheckedForUpdates, 13 | setError, 14 | setLoading, 15 | setThreads, 16 | setUpdate, 17 | } from '../../reducers/MainReducer/Actions'; 18 | import ThemeSelector from '../../utils/ThemeSelector'; 19 | import Updater from '../../utils/Updater'; 20 | import AlertDialog from '../AlertDialog'; 21 | import DrawerHeader from '../DrawerHeader'; 22 | import LoadingBar from '../LoadingBar'; 23 | import TopBar from '../TopBar'; 24 | import UpdateDialog from '../UpdateDialog'; 25 | 26 | const Layout = () => { 27 | const [state, d1] = useContext(MainContext); 28 | const { 29 | themeIndex, 30 | themeType, 31 | update, 32 | languageIndex, 33 | autoUpdate, 34 | error, 35 | loading, 36 | checkedForUpdates, 37 | } = state; 38 | 39 | const language = state.languages[languageIndex]; 40 | const color = ThemeSelector(themeIndex); 41 | 42 | const theme = createTheme({ 43 | palette: { 44 | primary: color, 45 | mode: themeType, 46 | }, 47 | }); 48 | 49 | /** 50 | * Check for updates 51 | */ 52 | const checkForUpdates = async () => { 53 | if (loading) { 54 | return; 55 | } 56 | 57 | d1(setUpdate(null)); 58 | d1(setError(null)); 59 | 60 | try { 61 | const res = platform(); 62 | const archRes = arch(); 63 | const ver = 'v' + (await getVersion()); 64 | 65 | Updater(res.toLowerCase(), archRes.toLowerCase(), ver) 66 | .then((up) => { 67 | d1(setUpdate(up)); 68 | }) 69 | .catch((err) => { 70 | d1(setError(err)); 71 | }); 72 | } catch (e) { 73 | d1(setError(e)); 74 | } 75 | d1(setLoading(false)); 76 | }; 77 | 78 | /** 79 | * Update the number of threads 80 | * @returns {Promise} 81 | */ 82 | const updateThreads = async () => { 83 | if (!localStorage.threads) { 84 | getNumberOfThreads() 85 | .then((res) => { 86 | d1(setThreads(res)); 87 | }) 88 | .catch(() => { 89 | d1(setThreads(1)); 90 | }); 91 | } 92 | }; 93 | 94 | /** 95 | * Close the dialog that displays a message that no updates are available 96 | */ 97 | const closeNoUpdate = () => { 98 | d1(setCheckedForUpdates(false)); 99 | }; 100 | 101 | /** 102 | * Close the alert dialog 103 | */ 104 | const closeAlertDialog = () => { 105 | d1(setError(null)); 106 | }; 107 | 108 | useEffect(() => { 109 | if (window.__TAURI__ && autoUpdate) { 110 | updateThreads(); 111 | checkForUpdates(); 112 | } 113 | // eslint-disable-next-line react-hooks/exhaustive-deps 114 | }, []); 115 | 116 | return ( 117 | 118 | 119 | 120 | 121 | 131 | 132 | }> 133 | 134 | 135 | 136 | 137 | {error && error.length > 0 ? ( 138 | 146 | ) : null} 147 | {} 148 | {update && update.updateAvailable ? ( 149 | d1(setUpdate(null))} 155 | updateAvailable={language.updateAvailable} 156 | newVersionText={language.newVersion} 157 | information={language.information} 158 | download={language.download} 159 | cancel={language.cancel} 160 | /> 161 | ) : update && !update.updateAvailable && checkedForUpdates ? ( 162 | 170 | ) : null} 171 | 172 | ); 173 | }; 174 | 175 | export default Layout; 176 | -------------------------------------------------------------------------------- /src/reducers/MainReducer/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | RESET_STATE, 3 | SET_ADDRESSES, 4 | SET_AUTO_UPDATE, 5 | SET_CHECKED_FOR_UPDATES, 6 | SET_COLOR_ON_DARK, 7 | SET_END_PORT, 8 | SET_ERROR, 9 | SET_IS_SCANNING, 10 | SET_LANGUAGE_INDEX, 11 | SET_LOADING, 12 | SET_NO_CLOSED, 13 | SET_PAGE_INDEX, 14 | SET_SCAN_RESULTS, 15 | SET_SORT, 16 | SET_START_PORT, 17 | SET_THEME_INDEX, 18 | SET_THEME_TYPE, 19 | SET_THREADS, 20 | SET_TIMEOUT, 21 | SET_UPDATE, 22 | SET_THEME_TOGGLE, 23 | SET_EXPORT_NO_CLOSED, 24 | SET_IS_CANCELLING, 25 | SET_NO_UNKNOWN, 26 | SET_EXPORT_NO_UNKNOWN, 27 | } from './Actions/actionTypes'; 28 | 29 | const MainReducer = (state, action) => { 30 | if (!action || !action.type) { 31 | return state; 32 | } 33 | switch (action.type) { 34 | case SET_LANGUAGE_INDEX: 35 | localStorage.languageIndex = action.payload; 36 | return { 37 | ...state, 38 | languageIndex: action.payload, 39 | }; 40 | case SET_THEME_INDEX: 41 | localStorage.themeIndex = action.payload; 42 | return { 43 | ...state, 44 | themeIndex: action.payload, 45 | }; 46 | case SET_THEME_TYPE: 47 | localStorage.themeType = action.payload; 48 | return { 49 | ...state, 50 | themeType: action.payload, 51 | }; 52 | case SET_PAGE_INDEX: 53 | return { 54 | ...state, 55 | pageIndex: action.payload, 56 | }; 57 | case RESET_STATE: 58 | localStorage.clear(); 59 | return { 60 | ...state, 61 | languageIndex: 0, 62 | themeIndex: 0, 63 | themeType: 'light', 64 | autoUpdate: true, 65 | colorOnDark: false, 66 | threads: 1, 67 | timeout: 300, 68 | noClosed: false, 69 | noHidden: false, 70 | sort: true, 71 | themeToggle: true, 72 | exportNoClosed: true, 73 | exportNoHidden: true, 74 | }; 75 | case SET_AUTO_UPDATE: 76 | localStorage.autoUpdate = action.payload; 77 | return { 78 | ...state, 79 | autoUpdate: action.payload, 80 | }; 81 | case SET_UPDATE: 82 | return { 83 | ...state, 84 | update: action.payload, 85 | }; 86 | case SET_LOADING: 87 | return { 88 | ...state, 89 | loading: action.payload, 90 | }; 91 | case SET_ERROR: 92 | return { 93 | ...state, 94 | error: action.payload, 95 | }; 96 | case SET_CHECKED_FOR_UPDATES: 97 | return { 98 | ...state, 99 | checkedForUpdates: action.payload, 100 | }; 101 | case SET_COLOR_ON_DARK: 102 | localStorage.colorOnDark = action.payload; 103 | return { 104 | ...state, 105 | colorOnDark: action.payload, 106 | }; 107 | case SET_ADDRESSES: 108 | // eslint-disable-next-line no-case-declarations 109 | const addresses = action.payload; 110 | for (let i = 0; i < addresses.length; i += 1) { 111 | if (addresses[i].startsWith('http://')) { 112 | addresses[i] = addresses[i].replace('http://', ''); 113 | } 114 | if (addresses[i].startsWith('https://')) { 115 | addresses[i] = addresses[i].replace('https://', ''); 116 | } 117 | } 118 | 119 | return { 120 | ...state, 121 | addresses, 122 | }; 123 | case SET_START_PORT: 124 | return { 125 | ...state, 126 | startPort: action.payload, 127 | }; 128 | case SET_END_PORT: 129 | return { 130 | ...state, 131 | endPort: action.payload, 132 | }; 133 | case SET_IS_SCANNING: 134 | return { 135 | ...state, 136 | isScanning: action.payload, 137 | }; 138 | case SET_THREADS: 139 | localStorage.threads = action.payload; 140 | return { 141 | ...state, 142 | threads: action.payload, 143 | }; 144 | case SET_TIMEOUT: 145 | localStorage.timeout = action.payload; 146 | return { 147 | ...state, 148 | timeout: action.payload, 149 | }; 150 | case SET_NO_CLOSED: 151 | localStorage.noClosed = action.payload; 152 | return { 153 | ...state, 154 | noClosed: action.payload, 155 | }; 156 | case SET_SORT: 157 | localStorage.sort = action.payload; 158 | return { 159 | ...state, 160 | sort: action.payload, 161 | }; 162 | case SET_SCAN_RESULTS: 163 | return { 164 | ...state, 165 | scanResults: action.payload, 166 | }; 167 | case SET_THEME_TOGGLE: 168 | localStorage.themeToggle = action.payload; 169 | return { 170 | ...state, 171 | themeToggle: action.payload, 172 | }; 173 | case SET_EXPORT_NO_CLOSED: 174 | localStorage.exportNoClosed = action.payload; 175 | return { 176 | ...state, 177 | exportNoClosed: action.payload, 178 | }; 179 | case SET_IS_CANCELLING: 180 | return { 181 | ...state, 182 | isCancelling: action.payload, 183 | }; 184 | case SET_NO_UNKNOWN: 185 | localStorage.noUnknown = action.payload; 186 | return { 187 | ...state, 188 | noUnknown: action.payload, 189 | }; 190 | case SET_EXPORT_NO_UNKNOWN: 191 | localStorage.exportNoUnknown = action.payload; 192 | return { 193 | ...state, 194 | exportNoUnknown: action.payload, 195 | }; 196 | default: 197 | return state; 198 | } 199 | }; 200 | 201 | export default MainReducer; 202 | -------------------------------------------------------------------------------- /src/components/TopBar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react'; 2 | import AttachMoneyIcon from '@mui/icons-material/AttachMoney'; 3 | import Brightness5Icon from '@mui/icons-material/Brightness5'; 4 | import Brightness7Icon from '@mui/icons-material/Brightness7'; 5 | import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; 6 | import ChevronRightIcon from '@mui/icons-material/ChevronRight'; 7 | import InfoIcon from '@mui/icons-material/Info'; 8 | import MenuIcon from '@mui/icons-material/Menu'; 9 | import SettingsIcon from '@mui/icons-material/Settings'; 10 | import TravelExploreIcon from '@mui/icons-material/TravelExplore'; 11 | import MuiAppBar from '@mui/material/AppBar'; 12 | import Divider from '@mui/material/Divider'; 13 | import MuiDrawer from '@mui/material/Drawer'; 14 | import IconButton from '@mui/material/IconButton'; 15 | import List from '@mui/material/List'; 16 | import ListItem from '@mui/material/ListItem'; 17 | import ListItemButton from '@mui/material/ListItemButton'; 18 | import ListItemIcon from '@mui/material/ListItemIcon'; 19 | import ListItemText from '@mui/material/ListItemText'; 20 | import { styled, useTheme } from '@mui/material/styles'; 21 | import Toolbar from '@mui/material/Toolbar'; 22 | import Typography from '@mui/material/Typography'; 23 | import { useNavigate } from 'react-router-dom'; 24 | import { MainContext } from '../../contexts/MainContextProvider'; 25 | import { openWebSite, setThemeType } from '../../reducers/MainReducer/Actions'; 26 | import DrawerHeader from '../DrawerHeader'; 27 | 28 | const drawerWidth = 240; 29 | 30 | const openedMixin = (theme) => ({ 31 | width: drawerWidth, 32 | transition: theme.transitions.create('width', { 33 | easing: theme.transitions.easing.sharp, 34 | duration: theme.transitions.duration.enteringScreen, 35 | }), 36 | overflowX: 'hidden', 37 | }); 38 | 39 | const closedMixin = (theme) => ({ 40 | transition: theme.transitions.create('width', { 41 | easing: theme.transitions.easing.sharp, 42 | duration: theme.transitions.duration.leavingScreen, 43 | }), 44 | overflowX: 'hidden', 45 | width: `calc(${theme.spacing(7)} + 1px)`, 46 | [theme.breakpoints.up('sm')]: { 47 | width: `calc(${theme.spacing(8)} + 1px)`, 48 | }, 49 | }); 50 | 51 | const AppBar = styled(MuiAppBar, { 52 | shouldForwardProp: (prop) => prop !== 'open', 53 | })(({ theme, open }) => ({ 54 | zIndex: theme.zIndex.drawer + 1, 55 | transition: theme.transitions.create(['width', 'margin'], { 56 | easing: theme.transitions.easing.sharp, 57 | duration: theme.transitions.duration.leavingScreen, 58 | }), 59 | ...(open && { 60 | marginLeft: drawerWidth, 61 | width: `calc(100% - ${drawerWidth}px)`, 62 | transition: theme.transitions.create(['width', 'margin'], { 63 | easing: theme.transitions.easing.sharp, 64 | duration: theme.transitions.duration.enteringScreen, 65 | }), 66 | }), 67 | })); 68 | 69 | const Drawer = styled(MuiDrawer, { 70 | shouldForwardProp: (prop) => prop !== 'open', 71 | })(({ theme, open }) => ({ 72 | width: drawerWidth, 73 | flexShrink: 0, 74 | whiteSpace: 'nowrap', 75 | boxSizing: 'border-box', 76 | ...(open && { 77 | ...openedMixin(theme), 78 | '& .MuiDrawer-paper': openedMixin(theme), 79 | }), 80 | ...(!open && { 81 | ...closedMixin(theme), 82 | '& .MuiDrawer-paper': closedMixin(theme), 83 | }), 84 | })); 85 | 86 | const TopBar = () => { 87 | const [state, d1] = useContext(MainContext); 88 | const { themeType, themeToggle } = state; 89 | 90 | const [open, setOpen] = useState(false); 91 | 92 | const { languageIndex, pageIndex, colorOnDark } = state; 93 | const language = state.languages[languageIndex]; 94 | 95 | const theme = useTheme(); 96 | const navigate = useNavigate(); 97 | 98 | /** 99 | * Open the drawer 100 | */ 101 | const handleDrawerOpen = () => { 102 | setOpen(true); 103 | }; 104 | 105 | /** 106 | * Close the drawer 107 | */ 108 | const handleDrawerClose = () => { 109 | setOpen(false); 110 | }; 111 | 112 | /** 113 | * Go to the about page 114 | */ 115 | const goToAbout = () => { 116 | navigate('/about'); 117 | handleDrawerClose(); 118 | }; 119 | 120 | /** 121 | * Go to the settings page 122 | */ 123 | const goToSettings = () => { 124 | navigate('/settings'); 125 | handleDrawerClose(); 126 | }; 127 | 128 | /** 129 | * Go to the home page 130 | */ 131 | const goHome = () => { 132 | navigate('/'); 133 | handleDrawerClose(); 134 | }; 135 | 136 | /** 137 | * Change the theme style 138 | */ 139 | const changeThemeStyle = () => { 140 | d1(setThemeType(themeType === 'dark' ? 'light' : 'dark')); 141 | }; 142 | 143 | return ( 144 | <> 145 | 146 | 147 | 157 | 158 | 159 | 166 | {language.applicationName} 167 | 168 |
169 | {themeToggle ? ( 170 | 175 | {themeType === 'dark' ? : } 176 | 177 | ) : null} 178 | 179 | 180 | 181 | 182 | 183 | {theme.direction === 'rtl' ? ( 184 | 185 | ) : ( 186 | 187 | )} 188 | 189 | 190 | 191 | 192 | 193 | 202 | 209 | 210 | 211 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 230 | 237 | 238 | 239 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | { 256 | openWebSite('https://codedead.com/donate'); 257 | }} 258 | > 259 | 266 | 267 | 268 | 272 | 273 | 274 | 275 | 284 | 291 | 292 | 293 | 297 | 298 | 299 | 300 | 301 | 302 | ); 303 | }; 304 | 305 | export default TopBar; 306 | -------------------------------------------------------------------------------- /src/routes/Settings/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react'; 2 | import Button from '@mui/material/Button'; 3 | import Card from '@mui/material/Card'; 4 | import CardContent from '@mui/material/CardContent'; 5 | import Checkbox from '@mui/material/Checkbox'; 6 | import { 7 | blue, 8 | lightBlue, 9 | red, 10 | green, 11 | lightGreen, 12 | purple, 13 | deepPurple, 14 | grey, 15 | orange, 16 | deepOrange, 17 | amber, 18 | brown, 19 | pink, 20 | indigo, 21 | cyan, 22 | teal, 23 | lime, 24 | yellow, 25 | } from '@mui/material/colors'; 26 | import Container from '@mui/material/Container'; 27 | import FormControl from '@mui/material/FormControl'; 28 | import FormControlLabel from '@mui/material/FormControlLabel'; 29 | import FormGroup from '@mui/material/FormGroup'; 30 | import FormLabel from '@mui/material/FormLabel'; 31 | import InputLabel from '@mui/material/InputLabel'; 32 | import MenuItem from '@mui/material/MenuItem'; 33 | import Radio from '@mui/material/Radio'; 34 | import RadioGroup from '@mui/material/RadioGroup'; 35 | import Select from '@mui/material/Select'; 36 | import TextField from '@mui/material/TextField'; 37 | import Typography from '@mui/material/Typography'; 38 | import { getVersion } from '@tauri-apps/api/app'; 39 | import { platform, arch } from '@tauri-apps/plugin-os'; 40 | import AlertDialog from '../../components/AlertDialog'; 41 | import GridList from '../../components/GridList'; 42 | import Theme from '../../components/Theme'; 43 | import { MainContext } from '../../contexts/MainContextProvider'; 44 | import { 45 | getNumberOfThreads, 46 | resetState, 47 | setAutoUpdate, 48 | setCheckedForUpdates, 49 | setColorOnDark, 50 | setError, 51 | setExportNoClosed, 52 | setExportNoUnknown, 53 | setLanguageIndex, 54 | setNoClosed, 55 | setNoUnknown, 56 | setPageIndex, 57 | setSort, 58 | setThemeIndex, 59 | setThemeToggle, 60 | setThemeType, 61 | setThreads, 62 | setTimeout, 63 | setUpdate, 64 | } from '../../reducers/MainReducer/Actions'; 65 | import Updater from '../../utils/Updater'; 66 | 67 | const Settings = () => { 68 | const [state, d1] = useContext(MainContext); 69 | 70 | const { 71 | languageIndex, 72 | autoUpdate, 73 | colorOnDark, 74 | themeIndex, 75 | themeType, 76 | threads, 77 | timeout, 78 | noClosed, 79 | sort, 80 | themeToggle, 81 | exportNoClosed, 82 | noUnknown, 83 | exportNoUnknown, 84 | } = state; 85 | const language = state.languages[languageIndex]; 86 | 87 | const [loading, setLoading] = useState(false); 88 | const [resetDialogOpen, setResetDialogOpen] = useState(false); 89 | 90 | /** 91 | * Change the theme 92 | * @param index The index of the theme 93 | */ 94 | const changeTheme = (index) => { 95 | d1(setThemeIndex(index)); 96 | }; 97 | 98 | /** 99 | * Dispatch an event to change the language 100 | * @param e The event that contains the language index 101 | */ 102 | const handleLanguageChange = (e) => { 103 | d1(setLanguageIndex(e.target.value)); 104 | }; 105 | 106 | /** 107 | * Change the theme style 108 | * @param event The event argument 109 | */ 110 | const changeThemeStyle = (event) => { 111 | d1(setThemeType(event.target.value)); 112 | }; 113 | 114 | /** 115 | * Change the number of threads 116 | * @param e The event argument 117 | */ 118 | const changeThreads = (e) => { 119 | if (parseInt(e.target.value, 10) < 1) return; 120 | d1(setThreads(parseInt(e.target.value, 10))); 121 | }; 122 | 123 | /** 124 | * Change the timeout value 125 | * @param e The event argument 126 | */ 127 | const changeTimeout = (e) => { 128 | if (parseInt(e.target.value, 10) < 1) return; 129 | d1(setTimeout(parseInt(e.target.value, 10))); 130 | }; 131 | 132 | /** 133 | * Reset all settings 134 | */ 135 | const resetSettings = () => { 136 | d1(resetState()); 137 | getNumberOfThreads() 138 | .then((res) => { 139 | d1(setThreads(res)); 140 | }) 141 | .catch(() => { 142 | d1(setThreads(1)); 143 | }); 144 | }; 145 | 146 | /** 147 | * Check for updates 148 | */ 149 | const checkForUpdates = async () => { 150 | if (loading) { 151 | return; 152 | } 153 | 154 | d1(setUpdate(null)); 155 | d1(setError(null)); 156 | 157 | try { 158 | const res = platform(); 159 | const archRes = arch(); 160 | const ver = 'v' + (await getVersion()); 161 | 162 | Updater(res.toLowerCase(), archRes.toLowerCase(), ver) 163 | .then((up) => { 164 | d1(setUpdate(up)); 165 | d1(setCheckedForUpdates(true)); 166 | }) 167 | .catch((error) => { 168 | d1(setError(error)); 169 | }); 170 | } catch (e) { 171 | d1(setError(e)); 172 | } 173 | setLoading(false); 174 | }; 175 | 176 | useEffect(() => { 177 | d1(setPageIndex(1)); 178 | // eslint-disable-next-line react-hooks/exhaustive-deps 179 | }, []); 180 | 181 | return ( 182 | 183 | 184 | {language.settings} 185 | 186 | 187 | 188 | 189 | d1(setAutoUpdate(e.target.checked))} 194 | value="autoUpdate" 195 | /> 196 | } 197 | label={language.autoUpdate} 198 | /> 199 | d1(setColorOnDark(e.target.checked))} 204 | value="colorOnDarkSelector" 205 | /> 206 | } 207 | label={language.colorOnDark} 208 | /> 209 | d1(setThemeToggle(e.target.checked))} 214 | value="themeToggleSelector" 215 | /> 216 | } 217 | label={language.themeToggleInTopBar} 218 | /> 219 | d1(setNoClosed(e.target.checked))} 224 | value="noClosed" 225 | /> 226 | } 227 | label={language.hideClosedPorts} 228 | /> 229 | d1(setNoUnknown(e.target.checked))} 234 | value="noUnknown" 235 | /> 236 | } 237 | label={language.hideUnknownPorts} 238 | /> 239 | d1(setExportNoClosed(e.target.checked))} 244 | value="exportNoClosed" 245 | /> 246 | } 247 | label={language.exportIncludeClosedPorts} 248 | /> 249 | d1(setExportNoUnknown(e.target.checked))} 254 | value="exportNoUnknown" 255 | /> 256 | } 257 | label={language.exportIncludeUnknownPorts} 258 | /> 259 | d1(setSort(e.target.checked))} 264 | value="sort" 265 | /> 266 | } 267 | label={language.sort} 268 | /> 269 | 270 | {language.language} 271 | 287 | 288 | 289 | 299 | 309 | 310 | 311 | 312 | 313 | 314 | changeTheme(0)} 320 | /> 321 | changeTheme(1)} 327 | /> 328 | changeTheme(2)} 334 | /> 335 | changeTheme(3)} 341 | /> 342 | changeTheme(4)} 348 | /> 349 | changeTheme(5)} 355 | /> 356 | changeTheme(6)} 362 | /> 363 | changeTheme(7)} 369 | /> 370 | changeTheme(8)} 376 | /> 377 | changeTheme(9)} 383 | /> 384 | changeTheme(10)} 390 | /> 391 | changeTheme(11)} 397 | /> 398 | changeTheme(12)} 404 | /> 405 | changeTheme(13)} 411 | /> 412 | changeTheme(14)} 418 | /> 419 | changeTheme(15)} 425 | /> 426 | changeTheme(16)} 432 | /> 433 | changeTheme(17)} 439 | /> 440 | 441 | 442 | {language.themeStyle} 443 | 444 | } 447 | label={language.light} 448 | /> 449 | } 452 | label={language.dark} 453 | /> 454 | 455 | 456 | 457 | 458 | 461 | 469 | resetSettings()} 474 | onCancel={() => setResetDialogOpen(false)} 475 | onClose={() => setResetDialogOpen(false)} 476 | agreeLabel={language.yes} 477 | cancelLabel={language.no} 478 | /> 479 | 480 | ); 481 | }; 482 | 483 | export default Settings; 484 | -------------------------------------------------------------------------------- /src/routes/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react'; 2 | import AddIcon from '@mui/icons-material/Add'; 3 | import RemoveIcon from '@mui/icons-material/Remove'; 4 | import Alert from '@mui/material/Alert'; 5 | import Button from '@mui/material/Button'; 6 | import Card from '@mui/material/Card'; 7 | import CardContent from '@mui/material/CardContent'; 8 | import Container from '@mui/material/Container'; 9 | import FormControl from '@mui/material/FormControl'; 10 | import Grid from '@mui/material/Grid'; 11 | import IconButton from '@mui/material/IconButton'; 12 | import InputLabel from '@mui/material/InputLabel'; 13 | import LinearProgress from '@mui/material/LinearProgress'; 14 | import MenuItem from '@mui/material/MenuItem'; 15 | import Paper from '@mui/material/Paper'; 16 | import Select from '@mui/material/Select'; 17 | import Snackbar from '@mui/material/Snackbar'; 18 | import TextField from '@mui/material/TextField'; 19 | import { DataGrid } from '@mui/x-data-grid'; 20 | import { invoke } from '@tauri-apps/api/core'; 21 | import { save } from '@tauri-apps/plugin-dialog'; 22 | import PortInput from '../../components/PortInput'; 23 | import { MainContext } from '../../contexts/MainContextProvider'; 24 | import { 25 | cancelScan, 26 | scanAddresses, 27 | setAddresses, 28 | setEndPort, 29 | setError, 30 | setIsCancelling, 31 | setIsScanning, 32 | setPageIndex, 33 | setScanResults, 34 | setStartPort, 35 | } from '../../reducers/MainReducer/Actions'; 36 | 37 | const Home = () => { 38 | const [state, d1] = useContext(MainContext); 39 | 40 | const { 41 | languages, 42 | languageIndex, 43 | addresses, 44 | startPort, 45 | endPort, 46 | timeout, 47 | threads, 48 | noClosed, 49 | sort, 50 | isScanning, 51 | scanResults, 52 | exportNoClosed, 53 | isCancelling, 54 | noUnknown, 55 | exportNoUnknown, 56 | } = state; 57 | const language = languages[languageIndex]; 58 | 59 | const [exportType, setExportType] = useState('application/json'); 60 | const [snackOpen, setSnackOpen] = useState(false); 61 | 62 | /** 63 | * Change the (IP) address 64 | * @param e The event argument 65 | * @param index The index of the address to modify 66 | */ 67 | const changeAddress = (e, index) => { 68 | const newAddresses = addresses.map((address, idx) => { 69 | if (index === idx) { 70 | return e.target.value; 71 | } 72 | return address; 73 | }); 74 | 75 | d1(setAddresses(newAddresses)); 76 | }; 77 | 78 | /** 79 | * Add an empty address 80 | */ 81 | const addAddress = () => { 82 | const newAddresses = [...addresses, '']; 83 | d1(setAddresses(newAddresses)); 84 | }; 85 | 86 | /** 87 | * Remove an address 88 | * @param indexToRemove The index of the address to remove 89 | */ 90 | const removeAddress = (indexToRemove) => { 91 | const newAddresses = addresses.filter( 92 | (address, index) => index !== indexToRemove, 93 | ); 94 | d1(setAddresses(newAddresses)); 95 | }; 96 | 97 | /** 98 | * Change the starting port 99 | * @param e The event argument 100 | */ 101 | const changeStartPort = (e) => { 102 | if (parseInt(e.target.value, 10) < 0) return; 103 | if (parseInt(e.target.value, 10) > 65535) return; 104 | 105 | if (parseInt(e.target.value, 10) > endPort) { 106 | // eslint-disable-next-line no-use-before-define 107 | changeEndPort({ target: { value: parseInt(e.target.value, 10) } }); 108 | } 109 | 110 | d1(setStartPort(parseInt(e.target.value, 10))); 111 | }; 112 | 113 | /** 114 | * Change the ending port 115 | * @param e The event argument 116 | */ 117 | const changeEndPort = (e) => { 118 | if (parseInt(e.target.value, 10) < 0) return; 119 | if (parseInt(e.target.value, 10) > 65535) return; 120 | 121 | if (parseInt(e.target.value, 10) < startPort) { 122 | changeStartPort({ target: { value: parseInt(e.target.value, 10) } }); 123 | } 124 | 125 | d1(setEndPort(parseInt(e.target.value, 10))); 126 | }; 127 | 128 | /** 129 | * Start (or cancel) a scan 130 | */ 131 | const startStopScan = () => { 132 | if (isScanning) { 133 | d1(setIsCancelling(true)); 134 | cancelScan().catch((err) => { 135 | d1(setError(err)); 136 | d1(setIsCancelling(false)); 137 | }); 138 | } else { 139 | for (let i = 0; i < addresses.length; i += 1) { 140 | if ( 141 | addresses[i] === '' || 142 | startPort < 0 || 143 | startPort > 65535 || 144 | endPort < 0 || 145 | endPort > 65535 || 146 | startPort > endPort 147 | ) 148 | return; 149 | } 150 | 151 | d1(setIsScanning(true)); 152 | d1(setScanResults(null)); 153 | 154 | scanAddresses(addresses, startPort, endPort, timeout, threads, sort) 155 | .then((res) => { 156 | d1(setScanResults(res)); 157 | }) 158 | .catch((err) => { 159 | d1(setError(err)); 160 | }) 161 | .finally(() => { 162 | d1(setIsScanning(false)); 163 | d1(setIsCancelling(false)); 164 | }); 165 | } 166 | }; 167 | 168 | /** 169 | * Clear the scan results 170 | */ 171 | const clearScanResults = () => { 172 | d1(setScanResults(null)); 173 | }; 174 | 175 | /** 176 | * Handle key down 177 | * @param event The event argument 178 | */ 179 | const handleKeyDown = (event) => { 180 | if (event.key === 'Enter') { 181 | startStopScan(); 182 | } 183 | }; 184 | 185 | /** 186 | * Set the export type 187 | * @param e The change event 188 | */ 189 | const handleExportTypeChange = (e) => { 190 | setExportType(e.target.value); 191 | }; 192 | 193 | /** 194 | * Close the snackbar 195 | */ 196 | const closeSnack = () => { 197 | setSnackOpen(false); 198 | }; 199 | 200 | /** 201 | * Get the export data 202 | * @param res The array of ScanResult objects 203 | * @param type The type of export 204 | * @returns {string} The export data 205 | */ 206 | const getExportData = (res, type) => { 207 | let toExport = ''; 208 | 209 | if (type === 'text/plain') { 210 | res.forEach((e) => { 211 | if ( 212 | (!exportNoClosed && e.portStatus === 'Closed') || 213 | (!exportNoUnknown && e.portStatus === 'Unknown') 214 | ) 215 | return; 216 | toExport += `${e.address} ${e.port} ${e.hostName} ${e.portStatus} ${e.scanDate}\n`; 217 | }); 218 | } else if (type === 'application/json') { 219 | let exportJson = JSON.parse(JSON.stringify(res)); 220 | if (!exportNoClosed) { 221 | exportJson = exportJson.filter((e) => e.portStatus !== 'Closed'); 222 | } 223 | if (!exportNoUnknown) { 224 | exportJson = exportJson.filter((e) => e.portStatus !== 'Unknown'); 225 | } 226 | toExport = JSON.stringify(exportJson, null, 2); 227 | } else if (type === 'text/csv') { 228 | res.forEach((e) => { 229 | if ( 230 | (!exportNoClosed && e.portStatus === 'Closed') || 231 | (!exportNoUnknown && e.portStatus === 'Unknown') 232 | ) 233 | return; 234 | toExport += `"${e.address.replaceAll('"', '""')}","${e.port}","${e.hostName.replaceAll('"', '""')}","${e.portStatus.replaceAll('"', '""')}","${e.scanDate.replaceAll('"', '""')}",\n`; 235 | }); 236 | } else if (type === 'text/html') { 237 | toExport = 238 | 'Advanced PortChecker'; 239 | res.forEach((e) => { 240 | if ( 241 | (!exportNoClosed && e.portStatus === 'Closed') || 242 | (!exportNoUnknown && e.portStatus === 'Unknown') 243 | ) 244 | return; 245 | toExport += ``; 246 | }); 247 | toExport += '
AddressPortHost NamePort StatusScan Date
${e.address}${e.port}${e.hostName}${e.portStatus}${e.scanDate}
'; 248 | } 249 | 250 | return toExport; 251 | }; 252 | 253 | /** 254 | * Export the data 255 | */ 256 | const onExport = () => { 257 | let ext = ''; 258 | switch (exportType) { 259 | case 'text/plain': 260 | ext = 'txt'; 261 | break; 262 | case 'application/json': 263 | ext = 'json'; 264 | break; 265 | case 'text/html': 266 | ext = 'html'; 267 | break; 268 | default: 269 | ext = 'csv'; 270 | break; 271 | } 272 | save({ 273 | multiple: false, 274 | filters: [ 275 | { 276 | name: exportType, 277 | extensions: [ext], 278 | }, 279 | ], 280 | }) 281 | .then((res) => { 282 | if (res && res.length > 0) { 283 | const resExt = res.slice(((res.lastIndexOf('.') - 1) >>> 0) + 2); 284 | const path = resExt && resExt.length > 0 ? res : `${res}.${ext}`; 285 | invoke('save_string_to_disk', { 286 | content: getExportData(scanResults, exportType), 287 | path, 288 | }) 289 | .then(() => { 290 | setSnackOpen(true); 291 | }) 292 | .catch((e) => { 293 | d1(setError(e)); 294 | }); 295 | } 296 | }) 297 | .catch((e) => { 298 | d1(setError(e)); 299 | }); 300 | }; 301 | 302 | /** 303 | * Create a data object 304 | * @param id The ID 305 | * @param addr The address 306 | * @param port The port 307 | * @param hostName The host name 308 | * @param portStatus The port status 309 | * @param scanDate The scan date 310 | * @returns {{hostName, portType, address, port, scanDate}} 311 | */ 312 | const createData = (id, addr, port, hostName, portStatus, scanDate) => ({ 313 | id, 314 | address: addr, 315 | port, 316 | hostName, 317 | portStatus, 318 | scanDate, 319 | }); 320 | 321 | const columns = [ 322 | { 323 | field: 'address', 324 | headerName: language.address, 325 | editable: false, 326 | flex: 1, 327 | }, 328 | { 329 | field: 'port', 330 | headerName: language.port, 331 | type: 'number', 332 | editable: false, 333 | }, 334 | { 335 | field: 'hostName', 336 | headerName: language.hostName, 337 | editable: false, 338 | flex: 1, 339 | }, 340 | { 341 | field: 'portStatus', 342 | headerName: language.portStatus, 343 | editable: false, 344 | flex: 1, 345 | }, 346 | { 347 | field: 'scanDate', 348 | headerName: language.scanDate, 349 | editable: false, 350 | flex: 1, 351 | }, 352 | ]; 353 | 354 | const scanResultRows = []; 355 | if (scanResults && scanResults.length > 0) { 356 | for (const res of scanResults) { 357 | if ( 358 | (noClosed && res.portStatus === 'Closed') || 359 | (noUnknown && res.portStatus === 'Unknown') 360 | ) { 361 | continue; 362 | } 363 | 364 | let portStatus = language.closed; 365 | if (res.portStatus === 'Open') { 366 | portStatus = language.open; 367 | } else if (res.portStatus === 'Unknown') { 368 | portStatus = language.unknown; 369 | } 370 | 371 | scanResultRows.push( 372 | createData( 373 | res.address + res.port, 374 | res.address, 375 | res.port, 376 | res.hostName, 377 | portStatus, 378 | res.scanDate, 379 | ), 380 | ); 381 | } 382 | } 383 | 384 | const addressElements = addresses.map((e, i) => { 385 | const canAdd = i === addresses.length - 1; 386 | 387 | return ( 388 | 0 ? 1 : 0 }}> 389 | 390 | changeAddress(event, i)} 399 | onKeyDown={handleKeyDown} 400 | /> 401 | 402 | {canAdd ? ( 403 | 404 | 410 | 411 | 412 | 413 | ) : ( 414 | 415 | removeAddress(i)} 419 | disabled={isScanning} 420 | > 421 | 422 | 423 | 424 | )} 425 | 426 | ); 427 | }); 428 | 429 | useEffect(() => { 430 | d1(setPageIndex(0)); 431 | // eslint-disable-next-line react-hooks/exhaustive-deps 432 | }, []); 433 | 434 | return ( 435 | 436 | 437 | 438 | {addressElements} 439 | 440 | 441 | 448 | 449 | 450 | 457 | 458 | 459 | 460 | 461 | 462 | 468 | 469 | {isScanning ? : null} 470 | 479 | 488 | 503 | 504 | {language.exportType} 505 | 519 | 520 | 521 | 522 | {language.exportSuccessful} 523 | 524 | 525 | 526 | ); 527 | }; 528 | 529 | export default Home; 530 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | use crate::result::{PortStatus, ScanResult}; 5 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, TcpStream, ToSocketAddrs}; 6 | use std::ops::Deref; 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | use std::sync::{Arc, Mutex}; 9 | use std::thread::available_parallelism; 10 | use std::time::Duration; 11 | use std::{fs, thread}; 12 | use tauri::Manager; 13 | 14 | mod result; 15 | 16 | struct SharedState { 17 | is_scanning: Arc, 18 | cancellation_token: Arc, 19 | last_error: Arc>, 20 | } 21 | 22 | fn main() { 23 | // Fix for NVIDIA 24 | #[cfg(target_os = "linux")] 25 | unsafe { 26 | std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1"); 27 | } 28 | 29 | let shared_state = SharedState { 30 | is_scanning: Arc::new(AtomicBool::new(false)), 31 | cancellation_token: Arc::new(AtomicBool::new(false)), 32 | last_error: Arc::new(Mutex::new(String::from(""))), 33 | }; 34 | 35 | tauri::Builder::default() 36 | .setup(|app| { 37 | #[cfg(debug_assertions)] // only include this code on debug builds 38 | { 39 | let window = app.get_webview_window("main").unwrap(); 40 | window.open_devtools(); 41 | } 42 | Ok(()) 43 | }) 44 | .plugin(tauri_plugin_os::init()) 45 | .plugin(tauri_plugin_dialog::init()) 46 | .manage(shared_state) 47 | .invoke_handler(tauri::generate_handler![ 48 | open_website, 49 | scan_port_range, 50 | cancel_scan, 51 | get_number_of_threads, 52 | save_string_to_disk 53 | ]) 54 | .run(tauri::generate_context!()) 55 | .expect("error while running tauri application"); 56 | } 57 | 58 | /// Save a string to disk 59 | /// 60 | /// # Arguments 61 | /// 62 | /// * `content` - The content that needs to be saved 63 | /// * `path` - The path where the content needs to be saved 64 | /// 65 | /// # Returns 66 | /// 67 | /// * `Ok(())` - If the content was saved successfully 68 | /// * `Err(String)` - If the content could not be saved 69 | #[tauri::command] 70 | fn save_string_to_disk(content: &str, path: &str) -> Result<(), String> { 71 | match fs::write(path, content) { 72 | Ok(_) => Ok(()), 73 | Err(e) => Err(e.to_string()), 74 | } 75 | } 76 | 77 | /// Get the number of threads that can be used for port scanning 78 | /// 79 | /// # Returns 80 | /// 81 | /// * `u32` - The number of threads that can be used for port scanning 82 | #[tauri::command] 83 | fn get_number_of_threads() -> usize { 84 | available_parallelism().unwrap().get() 85 | } 86 | 87 | /// Open a website using the default browser 88 | /// 89 | /// # Arguments 90 | /// 91 | /// * `website` - The website that needs to be opened 92 | /// 93 | /// # Returns 94 | /// 95 | /// * `Ok(())` - If the website was opened successfully 96 | /// * `Err(String)` - If the website could not be opened 97 | #[tauri::command] 98 | fn open_website(website: &str) -> Result<(), String> { 99 | match open::that(website) { 100 | Ok(_) => Ok(()), 101 | Err(e) => Err(e.to_string()), 102 | } 103 | } 104 | 105 | /// Cancel a port scan 106 | /// 107 | /// # Arguments 108 | /// 109 | /// * `state` - The shared state that contains the cancellation token 110 | /// 111 | /// # Returns 112 | /// 113 | /// * `Ok(())` - If the cancellation token was set successfully 114 | /// * `Err(String)` - If the cancellation token could not be set 115 | #[tauri::command] 116 | async fn cancel_scan(state: tauri::State<'_, SharedState>) -> Result<(), String> { 117 | if !state.is_scanning.load(Ordering::SeqCst) { 118 | return Err(String::from("No scan is currently running")); 119 | } 120 | 121 | state.cancellation_token.store(true, Ordering::SeqCst); 122 | state.is_scanning.store(false, Ordering::SeqCst); 123 | 124 | Ok(()) 125 | } 126 | 127 | /// Scan a range of ports for a specified host 128 | /// 129 | /// # Arguments 130 | /// 131 | /// * `state` - The shared state that contains the cancellation token 132 | /// * `address` - The host that needs to be scanned 133 | /// * `start_port` - The initial port that needs to be scanned 134 | /// * `end_port` - The final port that needs to be scanned 135 | /// * `timeout` - The connection timeout (in milliseconds) before a port is marked as closed 136 | /// * `threads` - The number of threads that should be used to scan the ports 137 | /// * `sort` - Whether the results should be sorted by port number 138 | /// 139 | /// # Returns 140 | /// 141 | /// * `Ok(Vec)` - If the scan was successful 142 | /// * `Err(String)` - If the scan was unsuccessful 143 | #[tauri::command] 144 | async fn scan_port_range( 145 | state: tauri::State<'_, SharedState>, 146 | addresses: Vec, 147 | start_port: u16, 148 | end_port: u16, 149 | timeout: u64, 150 | threads: usize, 151 | sort: bool, 152 | ) -> Result, String> { 153 | if state.is_scanning.load(Ordering::SeqCst) { 154 | return Err(String::from("A scan is already running")); 155 | } 156 | 157 | state.is_scanning.store(true, Ordering::SeqCst); 158 | state.last_error.lock().unwrap().clear(); 159 | 160 | let mut addresses_to_scan: Vec = vec![]; 161 | 162 | for address in addresses { 163 | let cancellation_token = Arc::clone(&state.cancellation_token); 164 | // Check the cancellation token and return if it's true 165 | if cancellation_token.load(Ordering::Relaxed) { 166 | state.is_scanning.store(false, Ordering::SeqCst); 167 | state.cancellation_token.store(false, Ordering::SeqCst); 168 | return Ok(vec![]); 169 | } 170 | 171 | let mut address_parts = address.splitn(2, '/'); 172 | let address = match address_parts.next() { 173 | Some(address) => address, 174 | None => { 175 | state.is_scanning.store(false, Ordering::SeqCst); 176 | state.cancellation_token.store(false, Ordering::SeqCst); 177 | return Err(format!("\"{}\" is an invalid address!", address)); 178 | } 179 | }; 180 | 181 | let subnet = address_parts.next(); 182 | if subnet.is_some() { 183 | let subnet_parts = match subnet { 184 | Some(subnet) => subnet, 185 | None => { 186 | state.is_scanning.store(false, Ordering::SeqCst); 187 | state.cancellation_token.store(false, Ordering::SeqCst); 188 | return Err(format!("\"{:?}\" is an invalid subnet mask!", subnet)); 189 | } 190 | }; 191 | let subnet = match subnet_parts.parse::() { 192 | Ok(subnet) => subnet, 193 | Err(_) => { 194 | state.is_scanning.store(false, Ordering::SeqCst); 195 | state.cancellation_token.store(false, Ordering::SeqCst); 196 | return Err(format!("\"{}\" is an invalid subnet mask!", subnet_parts)); 197 | } 198 | }; 199 | 200 | // Validate the subnet mask 201 | if subnet == 0 { 202 | state.is_scanning.store(false, Ordering::SeqCst); 203 | state.cancellation_token.store(false, Ordering::SeqCst); 204 | return Err(String::from("Subnet mask cannot be 0")); 205 | } 206 | 207 | // Use a dummy port to resolve a possible hostname to a parsable IP address 208 | let dummy_address_res = format!("{}:80", address).to_socket_addrs(); 209 | let mut dummy_address = match dummy_address_res { 210 | Ok(res) => res, 211 | Err(e) => { 212 | state.is_scanning.store(false, Ordering::SeqCst); 213 | state.cancellation_token.store(false, Ordering::SeqCst); 214 | return Err(format!( 215 | "{}:80 is an invalid socket address!\n{}", 216 | address, e 217 | )); 218 | } 219 | }; 220 | 221 | let socket_address = dummy_address.next().unwrap(); 222 | let ip_addr: IpAddr = socket_address.ip(); 223 | 224 | match ip_addr { 225 | IpAddr::V4(v4) => { 226 | // Validate the subnet mask 227 | if subnet > 32 { 228 | state.is_scanning.store(false, Ordering::SeqCst); 229 | state.cancellation_token.store(false, Ordering::SeqCst); 230 | return Err(format!("\"{}\" is an invalid subnet mask!", subnet)); 231 | } 232 | 233 | // Convert base IP address to a u32 integer 234 | let base_ip_u32 = u32::from(v4); 235 | 236 | // Calculate the subnet mask by shifting bits left 237 | let mask = !((1 << (32u8 - subnet)) - 1); 238 | 239 | // Get the network address by applying the subnet mask to the base IP 240 | let network_ip_u32 = base_ip_u32 & mask; 241 | 242 | // Calculate the number of host addresses in the subnet 243 | let num_addresses = 1 << (32 - subnet); 244 | 245 | for i in 0..num_addresses { 246 | let cancellation_token = Arc::clone(&state.cancellation_token); 247 | // Check the cancellation token and return if it's true 248 | if cancellation_token.load(Ordering::Relaxed) { 249 | state.is_scanning.store(false, Ordering::SeqCst); 250 | state.cancellation_token.store(false, Ordering::SeqCst); 251 | return Ok(vec![]); 252 | } 253 | 254 | let ip_u32 = network_ip_u32 + i; 255 | let ip = Ipv4Addr::from(ip_u32); 256 | addresses_to_scan.push(ip.to_string()); 257 | } 258 | } 259 | IpAddr::V6(v6) => { 260 | // Validate the subnet mask 261 | if subnet > 128 { 262 | state.is_scanning.store(false, Ordering::SeqCst); 263 | state.cancellation_token.store(false, Ordering::SeqCst); 264 | return Err(format!("\"{}\" is an invalid subnet mask", subnet)); 265 | } 266 | 267 | // Convert the IPv6 address to a u128 representation 268 | let ipv6_int = u128::from_be_bytes(v6.octets()); 269 | 270 | // Create the subnet mask 271 | let mask = !((1u128 << (128 - subnet)) - 1); 272 | 273 | // Calculate the network base address (first IP in the subnet) 274 | let network_base = ipv6_int & mask; 275 | 276 | // Calculate the range of addresses in the subnet 277 | let subnet_size = 1u128 << (128 - subnet); 278 | let last_ip = network_base + subnet_size - 1; 279 | 280 | // Iterate through all IP addresses in the subnet 281 | for ip_int in network_base..=last_ip { 282 | let cancellation_token = Arc::clone(&state.cancellation_token); 283 | // Check the cancellation token and return if it's true 284 | if cancellation_token.load(Ordering::Relaxed) { 285 | state.is_scanning.store(false, Ordering::SeqCst); 286 | state.cancellation_token.store(false, Ordering::SeqCst); 287 | return Ok(vec![]); 288 | } 289 | 290 | let ip_addr = Ipv6Addr::from(ip_int.to_be_bytes()); 291 | addresses_to_scan.push(ip_addr.to_string()); 292 | } 293 | } 294 | } 295 | } else { 296 | addresses_to_scan.push(address.to_string()); 297 | } 298 | } 299 | 300 | let mut threads = threads; 301 | let all_results: Arc>> = Arc::new(Mutex::new(vec![])); 302 | 303 | let mut scan_results: Vec = vec![]; 304 | for address in addresses_to_scan { 305 | // Check the cancellation token and return if it's true 306 | let cancellation_token = Arc::clone(&state.cancellation_token); 307 | if cancellation_token.load(Ordering::Relaxed) { 308 | state.is_scanning.store(false, Ordering::SeqCst); 309 | state.cancellation_token.store(false, Ordering::SeqCst); 310 | 311 | let res = all_results.lock().unwrap(); 312 | return Ok(res.deref().to_vec()); 313 | } 314 | 315 | for port in start_port..=end_port { 316 | match format!("{}:{}", &address, port).to_socket_addrs() { 317 | Ok(res) => res, 318 | Err(e) => { 319 | state.is_scanning.store(false, Ordering::SeqCst); 320 | state.cancellation_token.store(false, Ordering::SeqCst); 321 | return Err(format!( 322 | "{}:{} is an invalid socket address!\n{}", 323 | address, port, e 324 | )); 325 | } 326 | }; 327 | 328 | let scan_result = ScanResult::initialize(&address, port); 329 | scan_results.push(scan_result); 330 | } 331 | } 332 | 333 | if threads > 1 { 334 | if threads > scan_results.len() { 335 | threads = scan_results.len(); 336 | } 337 | 338 | // Divide the scan results into equal parts for each thread 339 | let range = scan_results.len() / threads; 340 | let remainder = scan_results.len() % threads; 341 | 342 | let mut current_start = 0; 343 | let mut current_end = range - 1; 344 | 345 | let mut handles = vec![]; 346 | for t in 0..threads { 347 | // Make sure the remainder is included in the last thread 348 | if t == threads - 1 && remainder > 0 { 349 | current_end += remainder; 350 | } 351 | 352 | let local_start = current_start; 353 | let local_end = current_end; 354 | 355 | let all_results = Arc::clone(&all_results); 356 | let cancellation_token = Arc::clone(&state.cancellation_token); 357 | let last_error = Arc::clone(&state.last_error); 358 | 359 | // Get a slice of the scan results for the current thread 360 | let scan_results_slice = scan_results[local_start..=local_end].to_vec(); 361 | 362 | let handle = thread::spawn(move || { 363 | let mut local_results = vec![]; 364 | for scan_result in scan_results_slice { 365 | if cancellation_token.load(Ordering::Relaxed) { 366 | break; 367 | } 368 | 369 | let address = scan_result.address.clone(); 370 | let port = scan_result.port; 371 | 372 | let res = match scan_request(scan_result, timeout) { 373 | Ok(r) => r, 374 | Err(e) => { 375 | let mut last_error = last_error.lock().unwrap(); 376 | *last_error = e.to_string(); 377 | ScanResult::new(&address, port, "", PortStatus::Unknown) 378 | } 379 | }; 380 | 381 | local_results.push(res); 382 | } 383 | 384 | // Append the results to the global results at the end of the thread 385 | let mut results = all_results.lock().unwrap(); 386 | results.append(&mut local_results); 387 | }); 388 | handles.push(handle); 389 | 390 | current_start = current_end + 1; 391 | current_end += range; 392 | } 393 | 394 | for handle in handles { 395 | handle.join().unwrap(); 396 | } 397 | } else { 398 | for scan_result in scan_results { 399 | // Check the cancellation token and return if it's true 400 | let cancellation_token = Arc::clone(&state.cancellation_token); 401 | if cancellation_token.load(Ordering::Relaxed) { 402 | state.is_scanning.store(false, Ordering::SeqCst); 403 | state.cancellation_token.store(false, Ordering::SeqCst); 404 | 405 | let res = all_results.lock().unwrap(); 406 | return Ok(res.deref().to_vec()); 407 | } 408 | 409 | let res = scan_request(scan_result, timeout); 410 | let res = match res { 411 | Ok(res) => res, 412 | Err(e) => { 413 | return Err(e.to_string()); 414 | } 415 | }; 416 | 417 | let mut all = all_results.lock().unwrap(); 418 | all.push(res); 419 | } 420 | } 421 | 422 | let mut res = all_results.lock().unwrap(); 423 | 424 | if sort { 425 | // Sort by port number 426 | res.sort_by(|a, b| a.port.cmp(&b.port)); 427 | } 428 | 429 | state.is_scanning.store(false, Ordering::SeqCst); 430 | state.cancellation_token.store(false, Ordering::SeqCst); 431 | 432 | if res.is_empty() { 433 | let last_error = state.last_error.lock().unwrap(); 434 | if !last_error.is_empty() { 435 | return Err(last_error.deref().to_string()); 436 | } 437 | } 438 | 439 | Ok(res.deref().to_vec()) 440 | } 441 | 442 | /// Scan a single port on a specified host 443 | /// 444 | /// # Arguments 445 | /// 446 | /// * `request` - The request that needs to be scanned 447 | /// * `timeout` - The connection timeout (in milliseconds) before a port is marked as closed 448 | /// 449 | /// # Returns 450 | /// 451 | /// * `Ok(ScanResult)` - If the scan was successful 452 | /// * `Err(String)` - If the scan was unsuccessful 453 | fn scan_request(mut request: ScanResult, timeout: u64) -> Result { 454 | let address = format!("{}:{}", request.address, request.port).to_socket_addrs(); 455 | let mut address = match address { 456 | Ok(res) => res, 457 | Err(e) => { 458 | return Err(e.to_string()); 459 | } 460 | }; 461 | 462 | let socket_address = address.next().unwrap(); 463 | let ip_addr: IpAddr = socket_address.ip(); 464 | let host_name = ip_addr.to_string(); 465 | 466 | if let Ok(stream) = TcpStream::connect_timeout(&socket_address, Duration::from_millis(timeout)) 467 | { 468 | request.set_scan_result(&host_name, PortStatus::Open); 469 | 470 | let res = stream.shutdown(Shutdown::Both); 471 | match res { 472 | Ok(_) => {} 473 | Err(e) => { 474 | println!("Unable to shut down TcpStream: {}", e) 475 | } 476 | } 477 | } else { 478 | request.set_scan_result(&host_name, PortStatus::Closed); 479 | } 480 | 481 | Ok(request) 482 | } 483 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------