mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
client/web: add eslint
Add eslint to require stricter typescript rules, particularly around required hook dependencies. This commit also updates any files that were now throwing errors with eslint. Updates #10261 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
parent
5a9e935597
commit
6e30c9d1fe
@ -8,19 +8,20 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"classnames": "^2.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"wouter": "^2.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"postcss": "^8.4.31",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.2",
|
||||
@ -35,11 +36,26 @@
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"start": "vite",
|
||||
"lint": "tsc --noEmit",
|
||||
"lint": "tsc --noEmit && eslint 'src/**/*.{ts,tsx,js,jsx}'",
|
||||
"test": "vitest",
|
||||
"format": "prettier --write 'src/**/*.{ts,tsx}'",
|
||||
"format-check": "prettier --check 'src/**/*.{ts,tsx}'"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
],
|
||||
"plugins": [
|
||||
"react-hooks"
|
||||
],
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "error"
|
||||
},
|
||||
"settings": {
|
||||
"projectRoot": "client/web/package.json"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 80
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import cx from "classnames"
|
||||
import React, { useEffect } from "react"
|
||||
import { ReactComponent as TailscaleIcon } from "src/assets/icons/tailscale-icon.svg"
|
||||
import LoginToggle from "src/components/login-toggle"
|
||||
@ -121,22 +120,3 @@ function Header({
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Footer({
|
||||
licensesURL,
|
||||
className,
|
||||
}: {
|
||||
licensesURL: string
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<footer className={cx("container max-w-lg mx-auto text-center", className)}>
|
||||
<a
|
||||
className="text-xs text-gray-500 hover:text-gray-600"
|
||||
href={licensesURL}
|
||||
>
|
||||
Open Source Licenses
|
||||
</a>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ export default function ExitNodeSelector({
|
||||
}
|
||||
}
|
||||
},
|
||||
[setOpen, selected, setSelected]
|
||||
[selected, updateNode, updatePrefs]
|
||||
)
|
||||
|
||||
const [
|
||||
@ -261,7 +261,7 @@ function ExitNodeSelectorInner({
|
||||
key={`${n.ID}-${n.Name}`}
|
||||
node={n}
|
||||
onSelect={() => onSelect(n)}
|
||||
isSelected={selected.ID == n.ID}
|
||||
isSelected={selected.ID === n.ID}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -309,7 +309,7 @@ function ExitNodeSelectorItem({
|
||||
|
||||
function CountryFlag({ code }: { code: string }) {
|
||||
return (
|
||||
countryFlags[code.toLowerCase()] || (
|
||||
<>{countryFlags[code.toLowerCase()]}</> || (
|
||||
<span className="font-medium text-gray-500 text-xs">
|
||||
{code.toUpperCase()}
|
||||
</span>
|
||||
|
@ -110,12 +110,7 @@ function LoginPopoverContent({
|
||||
setCanConnectOverTS(true)
|
||||
})
|
||||
.catch(() => setIsRunningCheck(false))
|
||||
}, [
|
||||
auth.viewerIdentity,
|
||||
isRunningCheck,
|
||||
setCanConnectOverTS,
|
||||
setIsRunningCheck,
|
||||
])
|
||||
}, [auth.viewerIdentity, isRunningCheck, node.IP])
|
||||
|
||||
/**
|
||||
* Checking connection for first time on page load.
|
||||
@ -125,6 +120,7 @@ function LoginPopoverContent({
|
||||
* leaving to turn on Tailscale then returning to the view.
|
||||
* See `onMouseEnter` on the div below.
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => checkTSConnection(), [])
|
||||
|
||||
const handleSignInClick = useCallback(() => {
|
||||
@ -145,7 +141,7 @@ function LoginPopoverContent({
|
||||
{auth.viewerIdentity && ` as ${auth.viewerIdentity.loginName}`}
|
||||
</div>
|
||||
{!auth.canManageNode &&
|
||||
(!auth.viewerIdentity || auth.authNeeded == AuthType.tailscale ? (
|
||||
(!auth.viewerIdentity || auth.authNeeded === AuthType.tailscale ? (
|
||||
<>
|
||||
<p className="text-neutral-500 text-xs">
|
||||
{auth.viewerIdentity ? (
|
||||
|
@ -125,6 +125,7 @@ export default function DeviceDetailsView({
|
||||
// TODO: pipe control serve url from backend
|
||||
href="https://login.tailscale.com/admin"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-indigo-700 text-sm"
|
||||
>
|
||||
this device’s page
|
||||
|
@ -32,7 +32,7 @@ export default function LoginView({
|
||||
return (
|
||||
<div className="mb-8 py-6 px-8 bg-white rounded-md shadow-2xl">
|
||||
<TailscaleIcon className="my-2 mb-8" />
|
||||
{data.Status == "Stopped" ? (
|
||||
{data.Status === "Stopped" ? (
|
||||
<>
|
||||
<div className="mb-6">
|
||||
<h3 className="text-3xl font-semibold mb-3">Connect</h3>
|
||||
@ -57,6 +57,7 @@ export default function LoginView({
|
||||
href="https://tailscale.com/kb/1028/key-expiry"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
learn more
|
||||
</a>
|
||||
@ -77,7 +78,12 @@ export default function LoginView({
|
||||
<p className="text-gray-700">
|
||||
Get started by logging in to your Tailscale network.
|
||||
Or, learn more at{" "}
|
||||
<a href="https://tailscale.com/" className="link" target="_blank">
|
||||
<a
|
||||
href="https://tailscale.com/"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
tailscale.com
|
||||
</a>
|
||||
.
|
||||
@ -103,6 +109,7 @@ export default function LoginView({
|
||||
href="https://tailscale.com/kb/1085/auth-keys/"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more →
|
||||
</a>
|
||||
|
@ -24,6 +24,7 @@ export default function SSHView({
|
||||
href="https://tailscale.com/kb/1193/tailscale-ssh/"
|
||||
className="text-indigo-700"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more →
|
||||
</a>
|
||||
@ -44,6 +45,7 @@ export default function SSHView({
|
||||
href="https://login.tailscale.com/admin/acls/"
|
||||
className="text-indigo-700"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
tailnet policy file
|
||||
</a>{" "}
|
||||
|
@ -65,17 +65,18 @@ export default function useAuth() {
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
}, [])
|
||||
}, [loadAuth])
|
||||
|
||||
useEffect(() => {
|
||||
loadAuth().then((d) => {
|
||||
if (
|
||||
!d.canManageNode &&
|
||||
new URLSearchParams(window.location.search).get("check") == "now"
|
||||
new URLSearchParams(window.location.search).get("check") === "now"
|
||||
) {
|
||||
newSession()
|
||||
}
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return {
|
||||
|
@ -71,6 +71,8 @@ export default function useExitNodes(tailnetName: string, filter?: string) {
|
||||
}
|
||||
}, [data, tailnetName])
|
||||
|
||||
const hasFilter = Boolean(filter)
|
||||
|
||||
const mullvadNodesSorted = useMemo(() => {
|
||||
const nodes: ExitNode[] = []
|
||||
|
||||
@ -91,7 +93,7 @@ export default function useExitNodes(tailnetName: string, filter?: string) {
|
||||
})
|
||||
}
|
||||
|
||||
if (!Boolean(filter)) {
|
||||
if (!hasFilter) {
|
||||
// When nothing is searched, only show a single best-matching
|
||||
// exit node per-country.
|
||||
//
|
||||
@ -121,7 +123,7 @@ export default function useExitNodes(tailnetName: string, filter?: string) {
|
||||
}
|
||||
|
||||
return nodes.sort(compareByName)
|
||||
}, [locationNodesMap, Boolean(filter)])
|
||||
}, [hasFilter, locationNodesMap])
|
||||
|
||||
// Ordered and filtered grouping of exit nodes.
|
||||
const exitNodeGroups = useMemo(() => {
|
||||
@ -165,7 +167,7 @@ function highestPriorityNode(nodes: ExitNode[]): ExitNode | undefined {
|
||||
|
||||
// compareName compares two exit nodes alphabetically by name.
|
||||
function compareByName(a: ExitNode, b: ExitNode): number {
|
||||
if (a.Location && b.Location && a.Location.Country == b.Location.Country) {
|
||||
if (a.Location && b.Location && a.Location.Country === b.Location.Country) {
|
||||
// Always put "<Country>: Best Match" node at top of country list.
|
||||
if (a.Name.includes(": Best Match")) {
|
||||
return -1
|
||||
|
@ -120,7 +120,7 @@ export default function useNodeData() {
|
||||
throw err
|
||||
})
|
||||
},
|
||||
[data]
|
||||
[data, isPosting, refreshData]
|
||||
)
|
||||
|
||||
const updatePrefs = useCallback(
|
||||
@ -169,7 +169,7 @@ export default function useNodeData() {
|
||||
}
|
||||
},
|
||||
// Run once.
|
||||
[]
|
||||
[refreshData]
|
||||
)
|
||||
|
||||
return { data, refreshData, updateNode, updatePrefs, isPosting }
|
||||
|
@ -29,15 +29,8 @@ export enum UpdateState {
|
||||
// useInstallUpdate initiates and tracks a Tailscale self-update via the LocalAPI,
|
||||
// and returns state messages showing the progress of the update.
|
||||
export function useInstallUpdate(currentVersion: string, cv?: VersionInfo) {
|
||||
if (!cv) {
|
||||
return {
|
||||
updateState: UpdateState.UpToDate,
|
||||
updateLog: "",
|
||||
}
|
||||
}
|
||||
|
||||
const [updateState, setUpdateState] = useState<UpdateState>(
|
||||
cv.RunningLatest ? UpdateState.UpToDate : UpdateState.Available
|
||||
cv?.RunningLatest ? UpdateState.UpToDate : UpdateState.Available
|
||||
)
|
||||
|
||||
const [updateLog, setUpdateLog] = useState<string>("")
|
||||
@ -132,7 +125,10 @@ export function useInstallUpdate(currentVersion: string, cv?: VersionInfo) {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = 0
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return { updateState, updateLog }
|
||||
return !cv
|
||||
? { updateState: UpdateState.UpToDate, updateLog: "" }
|
||||
: { updateState, updateLog }
|
||||
}
|
||||
|
3685
client/web/yarn.lock
3685
client/web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user