Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add network page #234

Merged
merged 17 commits into from
Feb 24, 2024
12 changes: 8 additions & 4 deletions internal/provider/linode/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ import (

func CreateHTTPClient(conf *settings.Settings) (*http.Client, error) {
transport := &http.Transport{}
transport, err := applyProxy(conf.Socks5Proxy, transport)
if err != nil {
log.Infof("Error connecting to proxy: '%s'", err)
log.Info("Continuing without proxy")
var err error

if conf.UseProxy && conf.Socks5Proxy != "" {
transport, err = applyProxy(conf.Socks5Proxy, transport)
if err != nil {
log.Errorf("Error connecting to proxy: '%s'", err)
log.Info("Continuing without proxy")
}
}

if conf.LoginToken == "" {
Expand Down
73 changes: 73 additions & 0 deletions internal/server/controllers/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package controllers

import (
"github.com/TimothyYe/godns/internal/settings"
"github.com/TimothyYe/godns/internal/utils"
"github.com/gofiber/fiber/v2"
log "github.com/sirupsen/logrus"
)

type NetworkSettings struct {
IPMode string `json:"ip_mode"`
IPUrls []string `json:"ip_urls"`
IPV6Urls []string `json:"ipv6_urls"`
UseProxy bool `json:"use_proxy"`
SkipSSLVerify bool `json:"skip_ssl_verify"`
Socks5Proxy string `json:"socks5_proxy"`
Webhook settings.Webhook `json:"webhook,omitempty"`
Resolver string `json:"resolver"`
IPInterface string `json:"ip_interface"`
}

func (c *Controller) GetNetworkSettings(ctx *fiber.Ctx) error {
settings := NetworkSettings{
IPMode: c.config.IPType,
IPUrls: c.config.IPUrls,
IPV6Urls: c.config.IPV6Urls,
UseProxy: c.config.UseProxy,
SkipSSLVerify: c.config.SkipSSLVerify,
Socks5Proxy: c.config.Socks5Proxy,
Webhook: c.config.Webhook,
Resolver: c.config.Resolver,
IPInterface: c.config.IPInterface,
}

return ctx.JSON(settings)
}

func (c *Controller) UpdateNetworkSettings(ctx *fiber.Ctx) error {
var settings NetworkSettings
if err := ctx.BodyParser(&settings); err != nil {
log.Errorf("Failed to parse request body: %s", err.Error())
return ctx.Status(400).SendString(err.Error())
}

if settings.IPMode == utils.IPV4 && len(settings.IPUrls) == 0 {
return ctx.Status(400).SendString("IP URLs cannot be empty")
}

if settings.IPMode == utils.IPV6 && len(settings.IPV6Urls) == 0 {
return ctx.Status(400).SendString("IPv6 URLs cannot be empty")
}

c.config.IPType = settings.IPMode
if settings.IPMode == utils.IPV6 {
c.config.IPV6Urls = settings.IPV6Urls
} else {
c.config.IPUrls = settings.IPUrls
}

c.config.UseProxy = settings.UseProxy
c.config.SkipSSLVerify = settings.SkipSSLVerify
c.config.Socks5Proxy = settings.Socks5Proxy
c.config.Webhook = settings.Webhook
c.config.Resolver = settings.Resolver
c.config.IPInterface = settings.IPInterface

if err := c.config.SaveSettings(c.configPath); err != nil {
log.Errorf("Failed to save settings: %s", err.Error())
return ctx.Status(500).SendString("Failed to save network settings")
}

return ctx.JSON(settings)
}
4 changes: 4 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ func (s *Server) initRoutes() {
route.Get("/provider/settings", s.controller.GetProviderSettings)
route.Put("/provider", s.controller.UpdateProvider)

// Network related routes
route.Get("/network", s.controller.GetNetworkSettings)
route.Put("/network", s.controller.UpdateNetworkSettings)

// Serve embedded files
s.app.Use("/", filesystem.New(filesystem.Config{
Root: http.FS(embeddedFiles),
Expand Down
55 changes: 55 additions & 0 deletions web/api/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { get_api_server } from "./env";

export interface WebHook {
enabled: boolean;
url: string;
request_body: string;
}

export interface NetworkSettings {
ip_mode: string;
ip_urls?: string[];
ipv6_urls?: string[];
use_proxy: boolean;
skip_ssl_verify: boolean;
socks5_proxy: string;
webhook: WebHook;
resolver: string;
ip_interface: string;
}

export async function get_network_settings(credentials: string): Promise<NetworkSettings> {
if (credentials) {
const resp = await fetch(get_api_server() + '/api/v1/network', {
method: 'GET',
headers: {
'Authorization': `Basic ${credentials}`
}
})

if (resp.status === 200) {
return resp.json();
}
}

return {} as NetworkSettings;
}

export async function update_network_settings(credentials: string, settings: NetworkSettings): Promise<boolean> {
if (credentials) {
const resp = await fetch(get_api_server() + '/api/v1/network', {
method: 'PUT',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(settings)
})

if (resp.status === 200) {
return true;
}
}

return false;
}
3 changes: 0 additions & 3 deletions web/app/domains/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ export default function Domains() {
<main className="flex min-h-screen flex-col items-center justify-start pt-10 max-w-screen-xl">
<ToastContainer />
<div className="flex flex-col items-center w-full bg-base-100 p-10">
{/* <div className="flex flex-row items-start w-full"> */}
{/* <span className="text-2xl font-semibold text-neutral-500 ml-1 mb-1">Provider</span> */}
{/* </div> */}
<ProviderControl />
<div className="divider"></div>
<DomainControl />
Expand Down
129 changes: 129 additions & 0 deletions web/app/network/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use client';

import { IpMode } from "@/components/ip-mode";
import { Proxy } from "@/components/proxy";
import { WebHook } from "@/components/webhook";
import { Resolver } from "@/components/resolver";
import { IPInterface } from "@/components/ip-interface";
import { useRouter } from "next/navigation";
import { CommonContext } from "@/components/user";
import { useEffect, useState, useContext } from "react";
import { get_network_settings, NetworkSettings, update_network_settings } from "@/api/network";
import { get_info } from "@/api/info";
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

export default function Network() {
const router = useRouter();
const userStore = useContext(CommonContext);
const { credentials, setCurrentPage, saveVersion } = userStore;
const [settings, setSettings] = useState<NetworkSettings>({} as NetworkSettings);

useEffect(() => {
if (!credentials) {
router.push('/login');
return;
}
setCurrentPage('Network');
get_info(credentials).then((info) => {
saveVersion(info.version);
});

get_network_settings(credentials).then((settings) => {
setSettings(settings);
});
}, [credentials, router, setCurrentPage, saveVersion]);


return (
<main className="flex min-h-screen flex-col items-center justify-between pt-10 max-w-screen-xl">
<ToastContainer />
<div className="p-5">
<div className="flex flex-col max-w-screen-lg gap-5">
<IpMode
IPMode={settings.ip_mode}
IPUrls={settings.ip_urls}
IPV6Urls={settings.ipv6_urls}
onIpModeChagne={(data) => {
setSettings({
...settings,
ip_mode: data.IPMode,
ip_urls: data.IPUrls,
ipv6_urls: data.IPV6Urls
});
}}
/>
<Proxy
EnableProxy={settings.use_proxy}
SkipSSLVerify={settings.skip_ssl_verify}
Socks5Proxy={settings.socks5_proxy}
onProxyChange={(data) => {
setSettings({
...settings,
use_proxy: data.EnableProxy,
skip_ssl_verify: data.SkipSSLVerify,
socks5_proxy: data.Socks5Proxy
});
}}
/>
{
settings.webhook ?
<WebHook
Enabled={settings.webhook.enabled}
Url={settings.webhook.url}
RequestBody={settings.webhook.request_body}
onWebHookChange={(data) => {
setSettings({
...settings,
webhook: {
enabled: data.Enabled,
url: data.Url,
request_body: data.RequestBody
}
});
}}
/> : null
}
<Resolver
Resolver={settings.resolver}
onResolverChange={(data) => {
setSettings({
...settings,
resolver: data.Resolver
});
}}
/>
<IPInterface
IPInterface={settings.ip_interface}
onIPInterfaceChange={(data) => {
setSettings({
...settings,
ip_interface: data.IPInterface
});
}}
/>
<div className="flex justify-center">
<button className="flex btn btn-primary"
onClick={() => {
if (!credentials) {
toast.error('Invalid credentials');
return;
}

update_network_settings(credentials, settings).then((success) => {
if (success) {
toast.success('Network settings updated successfully');
} else {
toast.error('Failed to update network settings');
}
});
}}
>
Save
</button>
</div>
</div>
</div>
</main>
);
}
7 changes: 7 additions & 0 deletions web/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ export const MoonFilledIcon = ({
</svg>
);

export const InterfaceIcon = ({
}) => (
<svg className="w-6 h-6 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 4h4m0 0v4m0-4-5 5M8 20H4m0 0v-4m0 4 5-5" />
</svg>
);

export const SunFilledIcon = ({
size = 24,
width,
Expand Down
37 changes: 37 additions & 0 deletions web/components/ip-interface.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { InterfaceIcon } from "./icons";

interface IPInterfaceProps {
IPInterface: string;
onIPInterfaceChange?: (data: IPInterfaceProps) => void;
}

export const IPInterface = (props: IPInterfaceProps) => {
return (
<div className="stats shadow bg-primary-content stats-vertical lg:stats-horizontal">
<div className="stat gap-2">
<div className="stat-title">IP Inerface</div>
<div className="flex flex-col gap-3">
<div className="flex flex-row items-center justify-start gap-2">
<span className="label-text text-slate-500 ">Set the network interface</span>
<div className="flex flex-grow justify-end text-secondary">
<InterfaceIcon />
</div>
</div>
<input
type="text"
className="input input-primary w-full"
placeholder="Input the network interface name: e.g. eth0"
value={props.IPInterface}
onChange={(e) => {
if (props.onIPInterfaceChange) {
props.onIPInterfaceChange({
IPInterface: e.target.value
});
}
}}
/>
</div>
</div>
</div>
)
}
Loading
Loading