mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a71389aa29 | ||
|
|
c3ba026452 | ||
|
|
193f8a7bdf | ||
|
|
f9814e1169 | ||
|
|
2f800bf912 | ||
|
|
0199ea81f3 | ||
|
|
a20894092b | ||
|
|
f4d6ce5770 | ||
|
|
dd67ab6dcb | ||
|
|
2613948cdf | ||
|
|
dc95d8bfd1 | ||
|
|
f49c28f550 | ||
|
|
2fc7b73ea0 | ||
|
|
a9354d3c87 | ||
|
|
e0ae7d068a | ||
|
|
ddd812e9a0 | ||
|
|
2d55d0736f | ||
|
|
8febc26f1f |
No files matched your search
@@ -0,0 +1,15 @@
|
||||
name: Codespell
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
with:
|
||||
only_warn: 1
|
||||
skip: package-lock.json,*.svg
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
# Wiretrustee Dashboard
|
||||
Wiretrustee Dashboard is a the Wiretrustee Managemenet server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
|
||||
Wiretrustee Dashboard is a the Wiretrustee Management server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
|
||||
## Tags
|
||||
```latest``` ```vX.X.X``` not available yet.
|
||||
|
||||
```main``` builded on every PR being merged to the repository
|
||||
```main``` built on every PR being merged to the repository
|
||||
## How to use this image
|
||||
HTTP run:
|
||||
```shell
|
||||
docker run -d --rm -p 80:80 wiretrustee/dashboard:main
|
||||
```
|
||||
Using SSL certificate from Let's Encript®:
|
||||
Using SSL certificate from Let's Encrypt®:
|
||||
```shell
|
||||
docker run -d --rm -p 80:80 -p 443:443 \
|
||||
-e LETSENCRYPT_DOMAIN=app.mydomain.com \
|
||||
|
||||
@@ -387,7 +387,7 @@ check_nb_domain() {
|
||||
|
||||
if [ "$DOMAIN" == "netbird.example.com" ]; then
|
||||
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
|
||||
retrun 1
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class AccessControlPage {
|
||||
}
|
||||
|
||||
async assertDefaultAccessCotrolIsCreated() {
|
||||
await test.step('Assert that default cotrol access is created', async () => {
|
||||
await test.step('Assert that default control access is created', async () => {
|
||||
await expect(this.defaulAccessControl).toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ const NameServerGroupAdd = () => {
|
||||
const inputNameRef = useRef<any>(null);
|
||||
const inputDescriptionRef = useRef<any>(null);
|
||||
const [selectCustom, setSelectCustom] = useState(false);
|
||||
const [matchDomains, setMatchDomains] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (editName)
|
||||
@@ -87,7 +88,6 @@ const NameServerGroupAdd = () => {
|
||||
cursor: "end",
|
||||
});
|
||||
}, [editDescription]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!nsGroup) return;
|
||||
|
||||
@@ -117,6 +117,7 @@ const NameServerGroupAdd = () => {
|
||||
nameservers: [] as NameServer[],
|
||||
groups: [],
|
||||
enabled: false,
|
||||
search_domains_enabled: false,
|
||||
} as NameServerGroup)
|
||||
);
|
||||
setEditName(false);
|
||||
@@ -125,6 +126,7 @@ const NameServerGroupAdd = () => {
|
||||
};
|
||||
|
||||
const onChange = (changedValues: any) => {
|
||||
|
||||
if (changedValues.primary !== undefined) {
|
||||
setIsPrimary(changedValues.primary);
|
||||
}
|
||||
@@ -156,6 +158,7 @@ const NameServerGroupAdd = () => {
|
||||
],
|
||||
groups: [],
|
||||
enabled: true,
|
||||
search_domains_enabled: false,
|
||||
},
|
||||
{
|
||||
name: cloudflareChoice,
|
||||
@@ -176,6 +179,7 @@ const NameServerGroupAdd = () => {
|
||||
],
|
||||
groups: [],
|
||||
enabled: true,
|
||||
search_domains_enabled: false,
|
||||
},
|
||||
{
|
||||
name: quad9Choice,
|
||||
@@ -196,6 +200,7 @@ const NameServerGroupAdd = () => {
|
||||
],
|
||||
groups: [],
|
||||
enabled: true,
|
||||
search_domains_enabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -203,6 +208,7 @@ const NameServerGroupAdd = () => {
|
||||
let nsGroupLocal = {} as NameServerGroup;
|
||||
if (value === customChoice) {
|
||||
nsGroupLocal = nsGroup;
|
||||
nsGroupLocal.domains = [];
|
||||
} else {
|
||||
defaultDNSOptions.forEach((nsg) => {
|
||||
if (value === nsg.name) {
|
||||
@@ -262,6 +268,8 @@ const NameServerGroupAdd = () => {
|
||||
groups: existingGroups,
|
||||
groupsToCreate: newGroups,
|
||||
enabled: values.enabled,
|
||||
search_domains_enabled:
|
||||
matchDomains > 0 ? formNSGroup.search_domains_enabled : false,
|
||||
} as NameServerGroupToSave;
|
||||
};
|
||||
|
||||
@@ -462,6 +470,7 @@ const NameServerGroupAdd = () => {
|
||||
</Col>
|
||||
</Space>
|
||||
</Row>
|
||||
{setMatchDomains(fields.length)}
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<Row key={index} style={{ marginBottom: "5px" }}>
|
||||
@@ -522,6 +531,13 @@ const NameServerGroupAdd = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeMarkDomain = (checked: boolean) => {
|
||||
setFormNSGroup({
|
||||
...formNSGroup,
|
||||
search_domains_enabled: checked,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{nsGroup && (
|
||||
@@ -701,6 +717,46 @@ const NameServerGroupAdd = () => {
|
||||
<Col span={24}>
|
||||
<Form.List name="domains">{renderDomains}</Form.List>
|
||||
</Col>
|
||||
|
||||
{matchDomains > 0 && (
|
||||
<Col span={24}>
|
||||
<Form.Item name="search_domains_enabled" label="">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
onChange={handleChangeMarkDomain}
|
||||
size="small"
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
Mark match domains as search domains
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "400",
|
||||
marginBottom: "0",
|
||||
}}
|
||||
>
|
||||
E.g., "peer.example.com" will be accessible with
|
||||
"peer"
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
<Col span={24}>
|
||||
<label
|
||||
style={{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -161,7 +161,7 @@ const Navbar = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.role?.toLowerCase() === "admin") {
|
||||
if (user.role?.toLowerCase() === "admin" || user.role?.toLowerCase() === "owner") {
|
||||
return true;
|
||||
}
|
||||
return !adminOnlyTabs.find((t) => t === key);
|
||||
|
||||
@@ -197,6 +197,7 @@ const PeerUpdate = (props: any) => {
|
||||
os !== "" &&
|
||||
!os.toLowerCase().startsWith("darwin") &&
|
||||
!os.toLowerCase().startsWith("windows") &&
|
||||
!os.toLowerCase().startsWith("ios") &&
|
||||
!os.toLowerCase().startsWith("android")
|
||||
);
|
||||
};
|
||||
|
||||
@@ -151,6 +151,7 @@ const RouteAddNew = (selectedPeer: any) => {
|
||||
!os.toLowerCase().startsWith("darwin") &&
|
||||
!os.toLowerCase().startsWith("windows") &&
|
||||
!os.toLowerCase().startsWith("android") &&
|
||||
!os.toLowerCase().startsWith("ios") &&
|
||||
route &&
|
||||
!routes
|
||||
.filter((r) => r.network_id === route.network_id)
|
||||
|
||||
@@ -100,6 +100,7 @@ const RoutePeerUpdate = () => {
|
||||
!os.toLowerCase().startsWith("darwin") &&
|
||||
!os.toLowerCase().startsWith("windows") &&
|
||||
!os.toLowerCase().startsWith("android") &&
|
||||
!os.toLowerCase().startsWith("ios") &&
|
||||
route &&
|
||||
!routes
|
||||
.filter((r) => r.network_id === route.network_id)
|
||||
|
||||
@@ -96,6 +96,7 @@ const RouteAddNew = () => {
|
||||
!os.toLowerCase().startsWith("darwin") &&
|
||||
!os.toLowerCase().startsWith("windows") &&
|
||||
!os.toLowerCase().startsWith("android") &&
|
||||
!os.toLowerCase().startsWith("ios") &&
|
||||
route &&
|
||||
!routes
|
||||
.filter((r) => r.network_id === route.network_id)
|
||||
|
||||
+23
-10
@@ -79,6 +79,7 @@ const UserEdit = (props: any) => {
|
||||
const [formUser, setFormUser] = useState({} as FormUser);
|
||||
const [form] = Form.useForm();
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [isOwner, setIsOwner] = useState(false);
|
||||
|
||||
const [confirmModal, confirmModalContextHolder] = Modal.useModal();
|
||||
|
||||
@@ -94,15 +95,21 @@ const UserEdit = (props: any) => {
|
||||
};
|
||||
|
||||
const createUserToSave = (values: any): UserToSave => {
|
||||
const autoGroups =
|
||||
groups
|
||||
?.filter((g) => values.autoGroupsNames.includes(g.id))
|
||||
.map((g) => g.id || "") || [];
|
||||
let autoGroups:string[] = []
|
||||
if (values.autoGroupsNames) {
|
||||
autoGroups =
|
||||
groups
|
||||
?.filter((g) => values.autoGroupsNames.includes(g.id))
|
||||
.map((g) => g.id || "") || [];
|
||||
}
|
||||
// find groups that do not yet exist (newly added by the user)
|
||||
const allGroupsNames: string[] = groups?.map((g) => g.id || "");
|
||||
const groupsToCreate = values.autoGroupsNames.filter(
|
||||
(s: string) => !allGroupsNames.includes(s)
|
||||
);
|
||||
let groupsToCreate:string[] = []
|
||||
if (values.autoGroupsNames) {
|
||||
groupsToCreate = values.autoGroupsNames.filter(
|
||||
(s: string) => !allGroupsNames.includes(s)
|
||||
);
|
||||
}
|
||||
let userID = user ? user.id : "";
|
||||
let isServiceUser = user ? user?.is_service_user : false;
|
||||
return {
|
||||
@@ -120,7 +127,8 @@ const UserEdit = (props: any) => {
|
||||
if (users) {
|
||||
let currentUser = users.find((user) => user?.is_current);
|
||||
if (currentUser) {
|
||||
setIsAdmin(currentUser.role === "admin");
|
||||
setIsAdmin(currentUser.role === "admin" || currentUser.role === "owner");
|
||||
setIsOwner(currentUser.role === "owner");
|
||||
}
|
||||
}
|
||||
}, [users]);
|
||||
@@ -403,7 +411,7 @@ const UserEdit = (props: any) => {
|
||||
>
|
||||
<Select
|
||||
style={{ width: "100%" }}
|
||||
disabled={user?.is_current}
|
||||
disabled={user?.is_current || user?.role === "owner"}
|
||||
>
|
||||
<Option value="admin">
|
||||
<Text type={"secondary"}>admin</Text>
|
||||
@@ -411,6 +419,11 @@ const UserEdit = (props: any) => {
|
||||
<Option value="user">
|
||||
<Text type={"secondary"}>user</Text>
|
||||
</Option>
|
||||
{!user?.is_service_user && isOwner && (
|
||||
<Option value="owner">
|
||||
<Text type={"secondary"}>owner</Text>
|
||||
</Option>
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
@@ -473,7 +486,7 @@ const UserEdit = (props: any) => {
|
||||
label="Block user"
|
||||
style={{ marginRight: "50px", fontWeight: "500" }}
|
||||
>
|
||||
<Switch />
|
||||
<Switch disabled={user?.role == "owner"} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ActionType, createAction, createAsyncAction} from 'typesafe-actions';
|
||||
import {Account} from './types';
|
||||
import {ApiError, ChangeResponse, RequestPayload} from '../../services/api-client/types';
|
||||
import {ApiError, ChangeResponse, DeleteResponse, RequestPayload} from '../../services/api-client/types';
|
||||
|
||||
const actions = {
|
||||
getAccounts: createAsyncAction(
|
||||
@@ -16,6 +16,13 @@ const actions = {
|
||||
)<RequestPayload<Account>, ChangeResponse<Account | null>, ChangeResponse<Account | null>>(),
|
||||
setUpdateAccount: createAction('SET_UPDATED_ACCOUNT')<ChangeResponse<Account | null>>(),
|
||||
resetUpdateAccount: createAction('RESET_UPDATED_ACCOUNT')<null>(),
|
||||
deleteAccount: createAsyncAction(
|
||||
'DELETE_ACCOUNT_REQUEST',
|
||||
'DELETE_ACCOUNT_SUCCESS',
|
||||
'DELETE_ACCOUNT_FAILURE'
|
||||
)<RequestPayload<string>, DeleteResponse<string | null>, DeleteResponse<string | null>>(),
|
||||
resetDeletedAccount: createAction('RESET_DELETED_ACCOUNT')<null>(),
|
||||
setDeleteAccount: createAction('SET_DELETE_ACCOUNT')<DeleteResponse<string | null>>(),
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,13 +2,14 @@ import { createReducer } from 'typesafe-actions';
|
||||
import { combineReducers } from 'redux';
|
||||
import { Account } from './types';
|
||||
import actions, { ActionTypes } from './actions';
|
||||
import {ApiError, ChangeResponse} from "../../services/api-client/types";
|
||||
import {ApiError, ChangeResponse, DeleteResponse} from "../../services/api-client/types";
|
||||
|
||||
type StateType = Readonly<{
|
||||
data: Account[] | null;
|
||||
loading: boolean;
|
||||
failed: ApiError | null;
|
||||
savedAccount: ChangeResponse<Account | null>;
|
||||
deletedAccount: DeleteResponse<string | null>;
|
||||
}>;
|
||||
|
||||
const initialState: StateType = {
|
||||
@@ -22,6 +23,13 @@ const initialState: StateType = {
|
||||
error: null,
|
||||
data : null
|
||||
},
|
||||
deletedAccount: <DeleteResponse<string | null>>{
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data : null
|
||||
},
|
||||
};
|
||||
|
||||
const data = createReducer<Account[], ActionTypes>(initialState.data as Account[])
|
||||
@@ -45,10 +53,18 @@ const updatedAccount = createReducer<ChangeResponse<Account | null>, ActionTypes
|
||||
.handleAction(actions.setUpdateAccount, (store, action) => action.payload)
|
||||
.handleAction(actions.resetUpdateAccount, () => initialState.savedAccount)
|
||||
|
||||
const deleteAccount = createReducer<DeleteResponse<string | null>, ActionTypes>(initialState.deletedAccount)
|
||||
.handleAction(actions.deleteAccount.request, () => initialState.deletedAccount)
|
||||
.handleAction(actions.deleteAccount.success, (store, action) => action.payload)
|
||||
.handleAction(actions.deleteAccount.failure, (store, action) => action.payload)
|
||||
.handleAction(actions.setDeleteAccount, (store, action) => action.payload)
|
||||
.handleAction(actions.resetDeletedAccount, () => initialState.deletedAccount)
|
||||
|
||||
|
||||
export default combineReducers({
|
||||
data,
|
||||
loading,
|
||||
failed,
|
||||
updatedAccount
|
||||
updatedAccount,
|
||||
deleteAccount
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import {all, call, put, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse, ChangeResponse} from '../../services/api-client/types';
|
||||
import {all, call, put, select, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse, ChangeResponse, DeleteResponse} from '../../services/api-client/types';
|
||||
import {Account} from './types'
|
||||
import service from './service';
|
||||
import actions from './actions';
|
||||
import {deletePeer} from "../peer/sagas";
|
||||
|
||||
export function* getAccounts(action: ReturnType<typeof actions.getAccounts.request>): Generator {
|
||||
try {
|
||||
@@ -55,10 +56,55 @@ export function* updateAccount(action: ReturnType<typeof actions.updateAccount.r
|
||||
}
|
||||
}
|
||||
|
||||
export function* deleteAccount(
|
||||
action: ReturnType<typeof actions.deleteAccount.request>
|
||||
): Generator {
|
||||
try {
|
||||
yield call(actions.setDeleteAccount, {
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null,
|
||||
} as DeleteResponse<string | null>);
|
||||
|
||||
const effect = yield call(service.deleteAccount, action.payload);
|
||||
const response = effect as ApiResponse<any>;
|
||||
|
||||
yield put(
|
||||
actions.deleteAccount.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body,
|
||||
} as DeleteResponse<string | null>)
|
||||
);
|
||||
|
||||
const accounts = (yield select((state) => state.peer.data)) as Account[];
|
||||
yield put(
|
||||
actions.getAccounts.success(
|
||||
accounts.filter((p: Account) => p.id !== action.payload.payload)
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
yield put(
|
||||
actions.deleteAccount.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: err as ApiError,
|
||||
data: null,
|
||||
} as DeleteResponse<string | null>)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function* sagas(): Generator {
|
||||
yield all([
|
||||
takeLatest(actions.getAccounts.request, getAccounts),
|
||||
takeLatest(actions.updateAccount.request, updateAccount),
|
||||
takeLatest(actions.deleteAccount.request, deleteAccount),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -15,5 +15,11 @@ export default {
|
||||
`/api/accounts/${id}`,
|
||||
payload
|
||||
);
|
||||
}
|
||||
},
|
||||
async deleteAccount(payload:RequestPayload<string>): Promise<ApiResponse<any>> {
|
||||
return apiClient.delete<any>(
|
||||
`/api/accounts/` + payload.payload,
|
||||
payload
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -8,6 +8,9 @@ export interface Account {
|
||||
jwt_groups_enabled: boolean;
|
||||
groups_propagation_enabled: boolean;
|
||||
jwt_groups_claim_name: string;
|
||||
extra: {
|
||||
peer_approval_enabled: boolean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,4 +20,5 @@ export interface FormAccount extends Account {
|
||||
groups_propagation_enabled: boolean;
|
||||
jwt_groups_claim_name: string;
|
||||
peer_login_expiration_formatted: ExpiresInValue;
|
||||
peer_approval_enabled: boolean;
|
||||
}
|
||||
@@ -69,8 +69,9 @@ export function* saveNameServerGroup(action: ReturnType<typeof actions.saveNameS
|
||||
nameservers: nameserverGroupToSave.nameservers,
|
||||
groups: newGroups,
|
||||
enabled: nameserverGroupToSave.enabled,
|
||||
} as NameServerGroup
|
||||
}
|
||||
search_domains_enabled: nameserverGroupToSave.search_domains_enabled,
|
||||
} as NameServerGroup,
|
||||
};
|
||||
|
||||
let effect
|
||||
if (!nameserverGroupToSave.id) {
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface NameServerGroup {
|
||||
nameservers: NameServer[];
|
||||
groups: string[];
|
||||
enabled: boolean;
|
||||
search_domains_enabled: boolean;
|
||||
}
|
||||
|
||||
export interface NameServer {
|
||||
|
||||
@@ -16,7 +16,8 @@ export interface Peer {
|
||||
dns_label: string,
|
||||
last_login: string,
|
||||
login_expired: boolean,
|
||||
login_expiration_enabled: boolean
|
||||
login_expiration_enabled: boolean,
|
||||
approval_required: boolean
|
||||
}
|
||||
|
||||
export interface FormPeer extends Peer {
|
||||
|
||||
Loaded 20 of 33 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user