Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b42ff51
feat: Add landscape laayout, file manager, and server stats (squash)
LukeGus Nov 21, 2025
0fe9e2b
fix: Improve UI issues
LukeGus Nov 24, 2025
623578b
feat: Greatly improve UI consistency, improve UI for all components (…
LukeGus Dec 1, 2025
8ee4177
feat: Improved file manager editor/styling
LukeGus Dec 4, 2025
f69db49
fix: Run npm linter and improve styling/UI bugs
LukeGus Dec 6, 2025
3691c50
feat: Fix tab bar height issues, and bein fixing terminal kb dismissal
LukeGus Dec 7, 2025
af9fd57
fix: Fix the snippets bar visibility and general issues with landscap…
LukeGus Dec 7, 2025
acddd26
chore: Format and clean up files
LukeGus Dec 7, 2025
553aa4b
Update app.json
LukeGus Dec 8, 2025
5cf46f4
fix: Updated types
LukeGus Dec 9, 2025
04a6b57
feat: Clean up files, add new termainl customization/tunnel system/ne…
LukeGus Dec 10, 2025
dc874c1
feat: Add terminal customization, totp/auth dialog support, and overa…
LukeGus Dec 12, 2025
900af50
feat: Add auto mosh support
LukeGus Dec 12, 2025
5032e54
fix: HTTP issues on iOS
LukeGus Dec 12, 2025
9d6407e
fix: increment ver
LukeGus Dec 12, 2025
2f21743
chore: Update readme
LukeGus Dec 12, 2025
87f20d8
fix: iOS not loading HTTP hosts
LukeGus Dec 13, 2025
2178e6e
feat: allow scrolling, backgrounddsiconnect fixes, http fixes, etc.
LukeGus Dec 14, 2025
6fbd66f
fix: Remove all bg conneciton bloat
LukeGus Dec 14, 2025
b80bf68
fix: improve reocnnection logic
LukeGus Dec 14, 2025
b133aa0
feat: improve copying and run cleanup
LukeGus Dec 14, 2025
6be099d
fix: http on ios
LukeGus Dec 15, 2025
61d5f2c
fix: http on ios
LukeGus Dec 15, 2025
135a49b
fix: build error with axios fetch
LukeGus Dec 15, 2025
b3dd300
feat: improe scrolling and htpp on ios
LukeGus Dec 15, 2025
539251d
fix: ios http
LukeGus Dec 16, 2025
7d2d3e8
fix: ui issues + cleanup for release cantidate
LukeGus Dec 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ If you would like, you can support the project here!\

# Overview

Termix Mobile is the official mobile companion app for Termix, providing remote SSH terminal control of your servers. You can connect to your existing Termix server configuration and manage all your SSH hosts with terminal capabilities optimized for mobile devices.
Full remote SSH control of your servers with Termix, the ultimate SSH server management tool. It connects to your existing Termix server to provide you with SSH server access.

# Planned Features

See [Projects](https://github.com/orgs/Termix-SSH/projects/2) for all planned features. If you are looking to contribute, see [Contributing](https://github.com/Termix-SSH/Mobile/blob/main/CONTRIBUTING.md).

# Features

- **SSH Terminal** - SSH terminal with multi-session support
- **Advanced Keyboard - Switch between two keyboard modes: the system keyboard and a custom terminal keyboard that is optimized for terminal use. The custom keyboard is completely configurable to your preferences.
- **Server Configuration** - Easily connect to your existing Termix server via IP/Domain. It has support for reverse proxy access login pages, logging in with OIDC, and, of course, regular username/password logins.
- **SSH Terminal** - SSH terminal with multi-session support. Switch between two keyboard modes: the system keyboard and a custom terminal keyboard that is optimized for terminal use. The custom keyboard is completely configurable to your preferences.
- **SSH File Manager** - View, edit, modify, and move files and folders via SSH.
- **SSH Server Stats** - Get information on a servers status such as CPU, RAM, HDD, etc.
- **SSH Tunnels** - Start, stop, and manage SSH tunnels.
- **Server Configuration** - Easily connect to your existing Termix server via IP/domain. It has support for reverse proxy access login pages, logging in with OIDC, and, of course, regular username/password logins.

# Installation

Expand Down
9 changes: 5 additions & 4 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"expo": {
"name": "Termix",
"slug": "termix",
"version": "1.1.0",
"orientation": "portrait",
"version": "1.2.0",
"orientation": "default",
"icon": "./assets/images/icon.png",
"scheme": "termix-mobile",
"githubUrl": "https://github.com/Termix-SSH/Mobile",
Expand Down Expand Up @@ -57,12 +57,13 @@
"usesCleartextTraffic": true
},
"ios": {
"newArchEnabled": true
"newArchEnabled": true,
"deploymentTarget": "15.1"
}
}
],
"./plugins/withNetworkSecurityConfig.js",
"./plugins/withIOSNetworkSecurity.js",
"./plugins/withNetworkSecurityConfig.js",
"expo-dev-client"
],
"experiments": {
Expand Down
10 changes: 10 additions & 0 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ import { Tabs, usePathname } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTerminalSessions } from "../contexts/TerminalSessionsContext";
import { useOrientation } from "../utils/orientation";
import { getTabBarHeight } from "../utils/responsive";

export default function TabLayout() {
const insets = useSafeAreaInsets();
const { sessions } = useTerminalSessions();
const pathname = usePathname();
const { isLandscape } = useOrientation();

const isSessionsTab = pathname === "/sessions";
const hasActiveSessions = sessions.length > 0;
const shouldHideMainTabBar = isSessionsTab && hasActiveSessions;

const tabBarHeight = getTabBarHeight(isLandscape);

return (
<Tabs
screenOptions={{
Expand All @@ -24,8 +29,13 @@ export default function TabLayout() {
borderTopWidth: 1.5,
borderTopColor: "#303032",
paddingBottom: insets.bottom,
height: tabBarHeight + insets.bottom,
},
headerShown: false,
tabBarLabelStyle: isLandscape
? { fontSize: 11, marginTop: -2 }
: undefined,
tabBarIconStyle: isLandscape ? { marginBottom: -2 } : undefined,
}}
>
<Tabs.Screen
Expand Down
2 changes: 1 addition & 1 deletion app/(tabs)/hosts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Hosts from "@/app/Tabs/Hosts/Hosts";
import Hosts from "@/app/tabs/hosts/Hosts";

export default function HostsScreen() {
return <Hosts />;
Expand Down
2 changes: 1 addition & 1 deletion app/(tabs)/sessions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Sessions from "@/app/Tabs/Sessions/Sessions";
import Sessions from "@/app/tabs/sessions/Sessions";

export default function SessionsScreen() {
return <Sessions />;
Expand Down
2 changes: 1 addition & 1 deletion app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Settings from "@/app/Tabs/Settings/Settings";
import Settings from "@/app/tabs/settings/Settings";

export default function SettingsScreen() {
return <Settings />;
Expand Down
49 changes: 49 additions & 0 deletions app/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import React, {
useState,
ReactNode,
useEffect,
useRef,
} from "react";
import { AppState, AppStateStatus } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
getVersionInfo,
Expand Down Expand Up @@ -176,6 +178,53 @@ export const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
});
}, []);

const lastValidationTimeRef = useRef<number>(0);
const validationInProgressRef = useRef<boolean>(false);

useEffect(() => {
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
if (
nextAppState === "active" &&
isAuthenticated &&
!validationInProgressRef.current
) {
const now = Date.now();
const timeSinceLastValidation = now - lastValidationTimeRef.current;

if (timeSinceLastValidation < 2000) {
return;
}

validationInProgressRef.current = true;
lastValidationTimeRef.current = now;

try {
const { getUserInfo } = await import("./main-axios");
const userInfo = await getUserInfo();

if (
!userInfo ||
!userInfo.username ||
userInfo.data_unlocked === false
) {
}
} catch (error) {
} finally {
validationInProgressRef.current = false;
}
}
};

const subscription = AppState.addEventListener(
"change",
handleAppStateChange,
);

return () => {
subscription.remove();
};
}, [isAuthenticated]);

return (
<AppContext.Provider
value={{
Expand Down
136 changes: 0 additions & 136 deletions app/Tabs/Sessions/KeyboardKey.tsx

This file was deleted.

Loading
Loading