diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index b72288a..bdcab42 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -1,26 +1,5 @@
-import { Ionicons } from "@expo/vector-icons";
-import { useRouter } from "expo-router";
import { Icon, Label, NativeTabs } from "expo-router/unstable-native-tabs";
import React from "react";
-import { StyleSheet, TouchableOpacity, View } from "react-native";
-import { useColorScheme } from "../../hooks/use-color-scheme";
-
-export function DevicesHeader() {
- const router = useRouter();
- const isDark = useColorScheme() === "dark";
- const activityColor = isDark ? "#0A84FF" : "#007AFF";
-
- return (
-
- router.push("/scan-devices")}
- style={styles.headerButton}
- >
-
-
-
- );
-}
export default function TabsLayout() {
return (
@@ -36,13 +15,3 @@ export default function TabsLayout() {
);
}
-
-const styles = StyleSheet.create({
- headerRight: {
- flexDirection: "row",
- gap: 16,
- },
- headerButton: {
- paddingHorizontal: 8,
- },
-});
diff --git a/app/_layout.tsx b/app/_layout.tsx
index a6d51ed..427fc3b 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -2,19 +2,21 @@ import { Ionicons } from '@expo/vector-icons';
import { Stack, useRouter, useSegments } from 'expo-router';
import { Text, TouchableOpacity } from 'react-native';
import { useColorScheme } from '../hooks/use-color-scheme';
-import { AuthProvider } from '../src/context/AuthContext';
+import { AuthProvider, useAuth } from '../src/context/AuthContext';
function DevicesHeader() {
const router = useRouter();
const isDark = useColorScheme() === 'dark';
const activityColor = isDark ? '#0A84FF' : '#007AFF';
+ const { canCreate } = useAuth();
return (
router.push('/scan-devices')}
+ disabled={!canCreate}
style={{ paddingHorizontal: 8 }}
>
-
+
);
}
diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx
index 0f5ed5a..b0f3ee4 100644
--- a/src/context/AuthContext.tsx
+++ b/src/context/AuthContext.tsx
@@ -9,6 +9,7 @@ interface AuthContextType {
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
+ canCreate: boolean;
login: (serverAddress: string, identity: string, password: string) => Promise;
logout: () => Promise;
}
@@ -20,6 +21,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const [serverAddress, setServerAddress] = useState(null);
const [token, setToken] = useState(null);
const [isLoading, setIsLoading] = useState(true);
+ const [canCreate, setCanCreate] = useState(false);
useEffect(() => {
loadAuth();
@@ -30,6 +32,11 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const storedToken = await AsyncStorage.getItem('auth_token');
const storedUser = await AsyncStorage.getItem('auth_user');
const storedServerAddress = await AsyncStorage.getItem('auth_server_address');
+ const storedCanCreate = await AsyncStorage.getItem('auth_can_create');
+
+ if (storedCanCreate) {
+ setCanCreate(storedCanCreate === 'true');
+ }
if (storedToken && storedUser) {
setToken(storedToken);
@@ -55,10 +62,12 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
await AsyncStorage.setItem('auth_token', response.token);
await AsyncStorage.setItem('auth_user', JSON.stringify(response.record));
await AsyncStorage.setItem('auth_server_address', serverAddress);
+ await AsyncStorage.setItem('auth_can_create', api.getCanCreate() ? 'true' : 'false');
setToken(response.token);
setUser(response.record);
setServerAddress(serverAddress);
+ setCanCreate(api.getCanCreate() || false);
} catch (error) {
throw error;
}
@@ -87,6 +96,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
token,
isAuthenticated: !!token && !!user,
isLoading,
+ canCreate,
login,
logout,
}}
diff --git a/src/services/api.ts b/src/services/api.ts
index 3b7aacc..16c67aa 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -1,256 +1,287 @@
-import { Device, AuthResponse, NetworkScanResult } from "../types";
+import {
+ Device,
+ AuthResponse,
+ NetworkScanResult,
+ PermissionResponse,
+} from '../types';
class UpSnapAPI {
- private token: string | null = null;
- private address: string | null = null;
+ private token: string | null = null;
+ private address: string | null = null;
+ private canCreate: boolean | null = null;
- setToken(token: string) {
- this.token = token;
- }
+ setToken(token: string) {
+ this.token = token;
+ }
- getToken(): string | null {
- return this.token;
- }
+ getToken(): string | null {
+ return this.token;
+ }
- clearToken() {
- this.token = null;
- }
+ clearToken() {
+ this.token = null;
+ }
- setAddress(address: string) {
- this.address = address;
- }
+ setAddress(address: string) {
+ this.address = address;
+ }
- getAddress(): string | null {
- return this.address;
- }
+ getAddress(): string | null {
+ return this.address;
+ }
- clearAddress() {
- this.address = null;
- }
+ clearAddress() {
+ this.address = null;
+ }
- private getHeaders(): HeadersInit {
- const headers: HeadersInit = {
- "Content-Type": "application/json",
- };
+ setCanCreate(canCreate: boolean) {
+ this.canCreate = canCreate;
+ }
- if (this.token) {
- headers["Authorization"] = `Bearer ${this.token}`;
- }
+ getCanCreate(): boolean | null {
+ return this.canCreate;
+ }
- return headers;
- }
+ clearCanCreate() {
+ this.canCreate = null;
+ }
- async authenticate(
- serverAddress: string,
- identity: string,
- password: string
- ): Promise {
- this.address = serverAddress + "/api";
+ private getHeaders(): HeadersInit {
+ const headers: HeadersInit = {
+ 'Content-Type': 'application/json',
+ };
- const response = await fetch(
- `${this.address}/collections/users/auth-with-password`,
- {
- method: "POST",
- headers: this.getHeaders(),
- body: JSON.stringify({ identity, password }),
- }
- );
+ if (this.token) {
+ headers['Authorization'] = `Bearer ${this.token}`;
+ }
- if (!response.ok) {
- const response = await fetch(
- `${this.address}/collections/_superusers/auth-with-password`,
- {
- method: "POST",
- headers: this.getHeaders(),
- body: JSON.stringify({ identity, password }),
- }
- );
+ return headers;
+ }
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.message || "Authentication failed");
- }
+ async authenticate(
+ serverAddress: string,
+ identity: string,
+ password: string
+ ): Promise {
+ this.address = serverAddress + '/api';
- const data: AuthResponse = await response.json();
- this.token = data.token;
- return data;
- }
+ const response = await fetch(
+ `${this.address}/collections/users/auth-with-password`,
+ {
+ method: 'POST',
+ headers: this.getHeaders(),
+ body: JSON.stringify({ identity, password }),
+ }
+ );
- const data: AuthResponse = await response.json();
- this.token = data.token;
- return data;
- }
+ if (!response.ok) {
+ const response = await fetch(
+ `${this.address}/collections/_superusers/auth-with-password`,
+ {
+ method: 'POST',
+ headers: this.getHeaders(),
+ body: JSON.stringify({ identity, password }),
+ }
+ );
- async getDevices(page = 1, perPage = 100): Promise {
- const response = await fetch(
- `${this.address}/collections/devices/records?page=${page}&perPage=${perPage}`,
- {
- headers: this.getHeaders(),
- }
- );
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.message || 'Authentication failed');
+ }
- if (!response.ok) {
- throw new Error("Failed to fetch devices");
- }
+ const data: AuthResponse = await response.json();
+ this.token = data.token;
+ this.canCreate = true;
+ return data;
+ }
- const data = await response.json();
- return data.items;
- }
+ const data: AuthResponse = await response.json();
+ this.token = data.token;
- async getDevice(id: string): Promise {
- const response = await fetch(
- `${this.address}/collections/devices/records/${id}`,
- {
- headers: this.getHeaders(),
- }
- );
+ const userID = data.record.id;
+ const userPermissionResponse = await fetch(
+ `${this.address}/collections/permissions/records/${userID}?expand=user,read,update,delete,power`,
+ {
+ headers: this.getHeaders(),
+ }
+ );
- if (!response.ok) {
- throw new Error("Failed to fetch device");
- }
+ const user: PermissionResponse = await userPermissionResponse.json();
+ this.canCreate = user.create;
- return response.json();
- }
+ return data;
+ }
- async createDevice(device: Partial): Promise {
- const response = await fetch(
- `${this.address}/collections/devices/records`,
- {
- method: "POST",
- headers: this.getHeaders(),
- body: JSON.stringify(device),
- }
- );
+ async getDevices(page = 1, perPage = 100): Promise {
+ const response = await fetch(
+ `${this.address}/collections/devices/records?page=${page}&perPage=${perPage}`,
+ {
+ headers: this.getHeaders(),
+ }
+ );
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.message || "Failed to create device");
- }
+ if (!response.ok) {
+ throw new Error('Failed to fetch devices');
+ }
- return response.json();
- }
+ const data = await response.json();
+ return data.items;
+ }
- async updateDevice(id: string, device: Partial): Promise {
- const response = await fetch(
- `${this.address}/collections/devices/records/${id}`,
- {
- method: "PATCH",
- headers: this.getHeaders(),
- body: JSON.stringify(device),
- }
- );
+ async getDevice(id: string): Promise {
+ const response = await fetch(
+ `${this.address}/collections/devices/records/${id}`,
+ {
+ headers: this.getHeaders(),
+ }
+ );
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.message || "Failed to update device");
- }
+ if (!response.ok) {
+ throw new Error('Failed to fetch device');
+ }
- return response.json();
- }
+ return response.json();
+ }
- async deleteDevice(id: string): Promise {
- const response = await fetch(
- `${this.address}/collections/devices/records/${id}`,
- {
- method: "DELETE",
- headers: this.getHeaders(),
- }
- );
+ async createDevice(device: Partial): Promise {
+ const response = await fetch(
+ `${this.address}/collections/devices/records`,
+ {
+ method: 'POST',
+ headers: this.getHeaders(),
+ body: JSON.stringify(device),
+ }
+ );
- if (!response.ok) {
- throw new Error("Failed to delete device");
- }
- }
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.message || 'Failed to create device');
+ }
- async wakeDevice(id: string): Promise {
- const response = await fetch(`${this.address}/upsnap/wake/${id}`, {
- headers: this.getHeaders(),
- });
+ return response.json();
+ }
- if (!response.ok) {
- throw new Error("Failed to wake device");
- }
- }
+ async updateDevice(id: string, device: Partial): Promise {
+ const response = await fetch(
+ `${this.address}/collections/devices/records/${id}`,
+ {
+ method: 'PATCH',
+ headers: this.getHeaders(),
+ body: JSON.stringify(device),
+ }
+ );
- async wakeGroup(id: string): Promise {
- const response = await fetch(`${this.address}/upsnap/wakegroup/${id}`, {
- headers: this.getHeaders(),
- });
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.message || 'Failed to update device');
+ }
- if (!response.ok) {
- throw new Error("Failed to wake group");
- }
- }
+ return response.json();
+ }
- async sleepDevice(id: string): Promise {
- const response = await fetch(`${this.address}/upsnap/sleep/${id}`, {
- headers: this.getHeaders(),
- });
+ async deleteDevice(id: string): Promise {
+ const response = await fetch(
+ `${this.address}/collections/devices/records/${id}`,
+ {
+ method: 'DELETE',
+ headers: this.getHeaders(),
+ }
+ );
- if (!response.ok) {
- throw new Error("Failed to sleep device");
- }
- }
+ if (!response.ok) {
+ throw new Error('Failed to delete device');
+ }
+ }
- async rebootDevice(id: string): Promise {
- const response = await fetch(`${this.address}/upsnap/reboot/${id}`, {
- headers: this.getHeaders(),
- });
+ async wakeDevice(id: string): Promise {
+ const response = await fetch(`${this.address}/upsnap/wake/${id}`, {
+ headers: this.getHeaders(),
+ });
- if (!response.ok) {
- throw new Error("Failed to reboot device");
- }
- }
+ if (!response.ok) {
+ throw new Error('Failed to wake device');
+ }
+ }
- async shutdownDevice(id: string): Promise {
- const response = await fetch(`${this.address}/upsnap/shutdown/${id}`, {
- headers: this.getHeaders(),
- });
+ async wakeGroup(id: string): Promise {
+ const response = await fetch(`${this.address}/upsnap/wakegroup/${id}`, {
+ headers: this.getHeaders(),
+ });
- if (!response.ok) {
- throw new Error("Failed to shutdown device");
- }
- }
+ if (!response.ok) {
+ throw new Error('Failed to wake group');
+ }
+ }
- async scanNetwork(): Promise {
- const response = await fetch(`${this.address}/upsnap/scan`, {
- headers: this.getHeaders(),
- });
+ async sleepDevice(id: string): Promise {
+ const response = await fetch(`${this.address}/upsnap/sleep/${id}`, {
+ headers: this.getHeaders(),
+ });
- if (!response.ok) {
- throw new Error("Failed to scan network");
- }
+ if (!response.ok) {
+ throw new Error('Failed to sleep device');
+ }
+ }
- const data = await response.json();
- if (data.devices && Array.isArray(data.devices)) {
- return data.devices.map((item: any) => ({
- name: item.name || item.hostname || "Unknown",
- ip: item.ip || item.ip_address || "",
- mac: item.mac || item.mac_address || "",
- mac_vendor: item.mac_vendor || "Unknown",
- }));
- }
+ async rebootDevice(id: string): Promise {
+ const response = await fetch(`${this.address}/upsnap/reboot/${id}`, {
+ headers: this.getHeaders(),
+ });
- if (Array.isArray(data)) {
- return data.map((item: any) => ({
- name: item.name || item.hostname || "Unknown",
- ip: item.ip || item.ip_address || "",
- mac: item.mac || item.mac_address || "",
- mac_vendor: item.mac_vendor || "Unknown",
- }));
- }
+ if (!response.ok) {
+ throw new Error('Failed to reboot device');
+ }
+ }
- if (data.items && Array.isArray(data.items)) {
- return data.items.map((item: any) => ({
- name: item.name || item.hostname || "Unknown",
- ip: item.ip || item.ip_address || "",
- mac: item.mac || item.mac_address || "",
- mac_vendor: item.mac_vendor || "Unknown",
- }));
- }
+ async shutdownDevice(id: string): Promise {
+ const response = await fetch(`${this.address}/upsnap/shutdown/${id}`, {
+ headers: this.getHeaders(),
+ });
- return [];
- }
+ if (!response.ok) {
+ throw new Error('Failed to shutdown device');
+ }
+ }
+
+ async scanNetwork(): Promise {
+ const response = await fetch(`${this.address}/upsnap/scan`, {
+ headers: this.getHeaders(),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to scan network');
+ }
+
+ const data = await response.json();
+ if (data.devices && Array.isArray(data.devices)) {
+ return data.devices.map((item: any) => ({
+ name: item.name || item.hostname || 'Unknown',
+ ip: item.ip || item.ip_address || '',
+ mac: item.mac || item.mac_address || '',
+ mac_vendor: item.mac_vendor || 'Unknown',
+ }));
+ }
+
+ if (Array.isArray(data)) {
+ return data.map((item: any) => ({
+ name: item.name || item.hostname || 'Unknown',
+ ip: item.ip || item.ip_address || '',
+ mac: item.mac || item.mac_address || '',
+ mac_vendor: item.mac_vendor || 'Unknown',
+ }));
+ }
+
+ if (data.items && Array.isArray(data.items)) {
+ return data.items.map((item: any) => ({
+ name: item.name || item.hostname || 'Unknown',
+ ip: item.ip || item.ip_address || '',
+ mac: item.mac || item.mac_address || '',
+ mac_vendor: item.mac_vendor || 'Unknown',
+ }));
+ }
+
+ return [];
+ }
}
export default new UpSnapAPI();
diff --git a/src/types/index.ts b/src/types/index.ts
index a8951db..61dd9d1 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -20,6 +20,20 @@ export interface AuthResponse {
record: User;
}
+export interface PermissionResponse {
+ id: string;
+ collectionId: string;
+ collectionName: string;
+ user: User;
+ create: boolean;
+ read: Device[];
+ update: Device[];
+ delete: Device[];
+ power: Device[];
+ created: string;
+ updated: string;
+}
+
export interface User {
id: string;
collectionId: string;