feat: add access profile selection support with device-specific profile persistence
Add SelectOwnProfile handler to allow users to choose from available access profiles. Store selected profile ID per device in settings table with device_access_profile category. Implement GetSelectedProfileID and SetSelectedProfileID repository methods using JSONB storage. Add ListSelectableProfiles to policy repository and service to query user/group/device-specific profiles ordered by priority. Filter gateway
This commit is contained in:
@@ -4,9 +4,19 @@ import { AppHeader } from "./components/AppHeader";
|
||||
import { ResourcePanel } from "./components/ResourcePanel";
|
||||
import { StatusCard } from "./components/StatusCard";
|
||||
|
||||
type AccessProfile = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
fullTunnel: boolean;
|
||||
destinations: string[];
|
||||
};
|
||||
|
||||
type EnrollmentState = {
|
||||
assignedIp: string;
|
||||
resources: string[];
|
||||
availableProfiles: AccessProfile[];
|
||||
selectedProfileId: string | null;
|
||||
profileRevision: number;
|
||||
gatewayEndpoint: string;
|
||||
profilePath: string;
|
||||
@@ -36,6 +46,11 @@ function currentProfileLabel(state: EnrollmentState | null) {
|
||||
return "Not provisioned";
|
||||
}
|
||||
|
||||
const selectedProfile = state.availableProfiles.find((profile) => profile.id === state.selectedProfileId);
|
||||
if (selectedProfile) {
|
||||
return selectedProfile.name;
|
||||
}
|
||||
|
||||
if (state.resources.includes("0.0.0.0/0")) {
|
||||
return "Full tunnel";
|
||||
}
|
||||
@@ -53,6 +68,7 @@ export function App() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [selectingProfile, setSelectingProfile] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [state, setState] = useState<EnrollmentState | null>(null);
|
||||
@@ -150,6 +166,25 @@ export function App() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onSelectProfile(profileId: string) {
|
||||
if (!state || profileId === state.selectedProfileId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectingProfile(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await invoke<EnrollmentState>("select_access_profile", { profileId });
|
||||
setState(result);
|
||||
await refreshTunnelStatus();
|
||||
} catch (err) {
|
||||
setError(formatInvokeError(err, "Profile selection failed"));
|
||||
} finally {
|
||||
setSelectingProfile(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleConnection() {
|
||||
const command = connected ? "disconnect_tunnel" : "connect_tunnel";
|
||||
try {
|
||||
@@ -248,9 +283,14 @@ export function App() {
|
||||
)}
|
||||
|
||||
<ResourcePanel
|
||||
connected={connected}
|
||||
onReset={resetEnrollment}
|
||||
onSelectProfile={onSelectProfile}
|
||||
profileLabel={profileLabel}
|
||||
profiles={state?.availableProfiles ?? []}
|
||||
resources={state?.resources ?? []}
|
||||
selectedProfileId={state?.selectedProfileId ?? null}
|
||||
selectingProfile={selectingProfile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user