lots of progress

This commit is contained in:
2025-03-31 23:25:59 +03:00
parent 5ef903b195
commit 7d6f72d31c
19 changed files with 1163 additions and 667 deletions
@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class PortScannerController extends Controller
{
public function index()
{
return view('port-scanner.index');
}
public function scan(Request $request)
{
$request->validate([
'ip' => 'required|ip',
'port' => 'required|integer|min:1|max:65535',
]);
$ip = $request->input('ip');
$port = $request->input('port');
$timeout = 2; // Timeout in seconds
try {
$startTime = microtime(true);
$connection = @fsockopen($ip, $port, $errno, $errstr, $timeout);
$endTime = microtime(true);
$responseTime = round(($endTime - $startTime) * 1000, 2); // Convert to milliseconds
if ($connection) {
fclose($connection);
$status = 'open';
$message = "Port $port is open on $ip";
} else {
$status = 'closed';
$message = "Port $port is closed on $ip";
}
return response()->json([
'success' => true,
'status' => $status,
'message' => $message,
'response_time' => $responseTime,
'ip' => $ip,
'port' => $port
]);
} catch (\Exception $e) {
Log::error('Port scan error', [
'ip' => $ip,
'port' => $port,
'error' => $e->getMessage()
]);
return response()->json([
'success' => false,
'message' => 'An error occurred while scanning the port',
'error' => $e->getMessage()
], 500);
}
}
}
-93
View File
@@ -1,93 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Services\ProxyService;
use Illuminate\Http\Request;
class ProxyController extends Controller
{
protected $proxyService;
public function __construct(ProxyService $proxyService)
{
$this->proxyService = $proxyService;
}
public function home()
{
return view('proxy.home');
}
public function browse(Request $request)
{
$request->validate([
'url' => 'required'
]);
try {
// Decode the URL if it's base64 encoded
$url = $request->url;
if (base64_encode(base64_decode($url, true)) === $url) {
// Decode base64 and then URL decode
$url = urldecode(base64_decode($url));
}
// Log the URL processing
\Log::info('Processing URL', [
'original' => $request->url,
'decoded' => $url
]);
// Validate the decoded URL
if (!filter_var($url, FILTER_VALIDATE_URL)) {
throw new \Exception('Invalid URL format: ' . $url);
}
$content = $this->proxyService->fetchUrl($url);
// Get content type from response
$contentType = $this->proxyService->getLastContentType();
// Log the content type for debugging
\Log::info('Content type received', [
'url' => $url,
'content_type' => $contentType,
'is_css' => strpos($contentType, 'text/css') !== false ||
strpos($contentType, 'application/css') !== false ||
strpos($url, '.css') !== false
]);
// If it's a CSS file, serve it with the correct content type
if (strpos($contentType, 'text/css') !== false ||
strpos($contentType, 'application/css') !== false ||
strpos($url, '.css') !== false) {
\Log::info('Serving CSS file', [
'url' => $url,
'content_type' => $contentType,
'content_length' => strlen($content)
]);
return response($content)
->header('Content-Type', 'text/css')
->header('Cache-Control', 'public, max-age=3600')
->header('Access-Control-Allow-Origin', '*')
->header('X-Content-Type-Options', 'nosniff');
}
// For HTML content, return the view
return view('proxy.browse', [
'url' => $url,
'content' => $content
]);
} catch (\Exception $e) {
\Log::error('Proxy error', [
'url' => $request->url,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return back()->withErrors(['url' => 'Failed to fetch URL: ' . $e->getMessage()]);
}
}
}
+6 -1
View File
@@ -2,12 +2,17 @@
namespace App\Http\Controllers;
use App\Models\Proxy;
use Illuminate\Http\Request;
class ProxyListController extends Controller
{
public function index()
{
return view('proxies.index');
$proxies = Proxy::orderBy('score', 'desc')
->orderBy('last_tested_at', 'desc')
->paginate(20);
return view('proxies.index', compact('proxies'));
}
}
+66 -37
View File
@@ -4,6 +4,7 @@ namespace App\Services;
use App\Models\Proxy;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\RequestException;
class ProxyService
@@ -17,6 +18,13 @@ class ProxyService
'timeout' => 30,
'verify' => false,
'http_errors' => false,
'cookies' => new CookieJar(), // Enables cookies
'allow_redirects' => [
'max' => 10, // Allow up to 10 redirects
'strict' => false,
'referer' => true,
'track_redirects' => true
],
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
@@ -44,6 +52,9 @@ class ProxyService
public function fetchUrl($url)
{
try {
$url = $this->replaceUrl($url);
// Normalize and encode the URL
$url = $this->normalizeUrl($url);
@@ -80,9 +91,20 @@ class ProxyService
// Add Host header to maintain virtual hosting
$response = $this->client->get($url, [
'headers' => [
'Host' => $host
'Host' => $host,
'Accept-Encoding' => 'identity' // Request uncompressed data
]
]);
$finalUrl = $response->getHeaderLine('X-Guzzle-Redirect-History') ?: $url;
// If redirected to a YouTube consent page, modify the request
if (strpos($finalUrl, 'consent.youtube.com') !== false) {
\Log::info('YouTube consent page detected, bypassing...');
$url = str_replace('consent.youtube.com', 'www.youtube.com', $finalUrl);
$response = $this->client->get($url);
}
} catch (\Exception $dnsError) {
\Log::error('DNS resolution failed', [
'url' => $url,
@@ -176,7 +198,7 @@ class ProxyService
return $this->processCssContent($content, $baseUrl);
}
// Process stylesheet links that don't start with http or https
// Process stylesheet links first
$content = preg_replace_callback(
'/<link[^>]*rel=["\']stylesheet["\'][^>]*href=["\'](?!https?:\/\/)([^"\']+)["\']/i',
function($matches) use ($baseUrl) {
@@ -201,15 +223,9 @@ class ProxyService
$content
);
// For HTML content, process only anchor tags with external URLs
$patterns = [
// External links in href attributes (only for anchor tags)
'/(<a[^>]*href)=["\'](https?:\/\/[^"\']+)["\']/i'
];
foreach ($patterns as $pattern) {
// Process external links (only anchor tags)
$content = preg_replace_callback(
$pattern,
'/(<a[^>]*href)=["\'](https?:\/\/[^"\']+)["\']/i',
function($matches) use ($baseUrl) {
$url = $matches[2];
@@ -217,35 +233,28 @@ class ProxyService
if (strpos($url, route('proxy.browse')) === 0) {
return $matches[0];
}
// Skip URLs that are already proxied
if (strpos($url, route('proxy.browse')) === 0) {
// Skip URLs from the same domain
if (strpos($url, parse_url($baseUrl, PHP_URL_HOST)) !== false) {
return $matches[0];
}
// Proxy all http/https URLs
if (preg_match('/^https?:\/\//i', $url)) {
$url = route('proxy.browse') . '?url=' . base64_encode($url);
return $matches[1] . '="' . htmlspecialchars($url) . '"';
}
// Only proxy external URLs
if (strpos($url, 'http') === 0 && strpos($url, parse_url($baseUrl, PHP_URL_HOST)) === false) {
$url = route('proxy.browse') . '?url=' . base64_encode($url);
}
// Proxy external URLs
$encodedUrl = base64_encode($url);
$proxiedUrl = route('proxy.browse') . '?url=' . $encodedUrl;
// Log the URL transformation
\Log::info('URL transformed', [
'original' => $matches[0],
'transformed' => $url
'transformed' => $proxiedUrl
]);
return $matches[1] . '="' . htmlspecialchars($url) . '"';
return $matches[1] . '="' . htmlspecialchars($proxiedUrl) . '"';
},
$content
);
}
// Add JavaScript to handle dynamic links and resources
// Add JavaScript to handle dynamic content
$content = $this->addLinkHandlerScript($content);
return $content;
@@ -359,21 +368,31 @@ class ProxyService
document.addEventListener('DOMContentLoaded', function() {
// Function to encode URL using base64
function encodeUrl(url) {
// First encode the URL to handle special characters
var encoded = encodeURIComponent(url);
// Then convert to base64
return btoa(encoded);
return btoa(encodeURIComponent(url));
}
// Function to convert relative URL to absolute
function toAbsoluteUrl(url) {
if (!url) return url;
if (url.startsWith('http')) return url;
if (url.startsWith('//')) return window.location.protocol + url;
if (url.startsWith('/')) return window.location.origin + url;
return window.location.origin + '/' + url;
}
// Handle dynamically added links
// Function to check if URL should be proxied
function shouldProxyUrl(url) {
if (!url) return false;
if (url.startsWith('{{ route('proxy.browse') }}')) return false;
if (url.startsWith('data:')) return false;
if (url.startsWith('javascript:')) return false;
if (url.startsWith('mailto:')) return false;
if (url.startsWith('tel:')) return false;
if (url.includes('youtube.com') && !url.includes('watch?')) return false; // Allow YouTube navigation
return url.startsWith('http') && !url.includes(window.location.hostname);
}
// Handle clicks on anchor tags
document.addEventListener('click', function(e) {
var target = e.target;
while (target && target.tagName !== 'A') {
@@ -383,7 +402,7 @@ document.addEventListener('DOMContentLoaded', function() {
var href = target.getAttribute('href');
if (href) {
href = toAbsoluteUrl(href);
if (href.startsWith('http') && !href.includes(window.location.hostname)) {
if (shouldProxyUrl(href)) {
e.preventDefault();
var encodedUrl = encodeUrl(href);
window.location.href = '{{ route('proxy.browse') }}?url=' + encodedUrl;
@@ -398,7 +417,7 @@ document.addEventListener('DOMContentLoaded', function() {
var action = form.getAttribute('action');
if (action) {
action = toAbsoluteUrl(action);
if (action.startsWith('http') && !action.includes(window.location.hostname)) {
if (shouldProxyUrl(action)) {
e.preventDefault();
var encodedUrl = encodeUrl(action);
form.action = '{{ route('proxy.browse') }}?url=' + encodedUrl;
@@ -413,7 +432,7 @@ document.addEventListener('DOMContentLoaded', function() {
var href = link.getAttribute('href');
if (href) {
href = toAbsoluteUrl(href);
if (href.startsWith('http') && !href.includes(window.location.hostname)) {
if (shouldProxyUrl(href)) {
var encodedUrl = encodeUrl(href);
link.setAttribute('href', '{{ route('proxy.browse') }}?url=' + encodedUrl);
}
@@ -429,12 +448,11 @@ document.addEventListener('DOMContentLoaded', function() {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) { // Element node
// Only process anchor tags
if (node.tagName === 'A') {
var href = node.getAttribute('href');
if (href) {
href = toAbsoluteUrl(href);
if (href.startsWith('http') && !href.includes(window.location.hostname)) {
if (shouldProxyUrl(href)) {
var encodedUrl = encodeUrl(href);
node.setAttribute('href', '{{ route('proxy.browse') }}?url=' + encodedUrl);
}
@@ -445,7 +463,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
// Start observing the document with the configured parameters
// Start observing the document
observer.observe(document.documentElement, { childList: true, subtree: true });
});
</script>
@@ -456,4 +474,15 @@ EOT;
return $content;
}
private function replaceUrl($url)
{
$parsedUrl = parse_url($url);
if ($parsedUrl['host'] === 'www.youtube.com') {
$url = str_replace('www.youtube.com', 'tube.cadence.moe', $url);
}
return $url;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Services;
class Utils
{
public static function proxifyUrl(string $url, string $siteBaseUrl)
{
if (!self::isNotAbsoluteUrl($url)) {
// should append site url
$url = $siteBaseUrl.$url;
}
return route('proxy.browse') . '?url=' . base64_encode($url);
}
public static function isNotAbsoluteUrl($url) {
return parse_url($url, PHP_URL_SCHEME) !== null;
}
public static function isAbsoluteUrl($url) {
return parse_url($url, PHP_URL_SCHEME) === null;
}
}
+4 -5
View File
@@ -6,15 +6,14 @@
"dev": "vite"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/vite": "^4.0.0",
"alpinejs": "^3.4.2",
"autoprefixer": "^10.4.2",
"axios": "^1.8.2",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.31",
"tailwindcss": "^3.1.0",
"vite": "^6.0.11"
},
"dependencies": {
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3"
}
}
+97 -3
View File
@@ -1,3 +1,97 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom Bootstrap Variables */
:root {
--bs-primary: #0d6efd;
--bs-primary-rgb: 13, 110, 253;
--bs-link-color: #0d6efd;
--bs-link-hover-color: #0a58ca;
--bs-body-font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
/* Custom Styles */
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
}
.navbar {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.8) !important;
}
.navbar-brand {
font-weight: 600;
}
.nav-link {
font-weight: 500;
position: relative;
transition: color 0.3s ease;
}
.nav-link::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: -2px;
left: 0;
background-color: currentColor;
transition: width 0.3s ease;
}
.nav-link:hover::after {
width: 100%;
}
.btn-primary {
background: linear-gradient(135deg, var(--bs-primary) 0%, var(--bs-link-hover-color) 100%);
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(var(--bs-primary-rgb), 0.3);
}
.card {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
/* Form Controls */
.form-control:focus {
border-color: var(--bs-primary);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
}
/* Dropdowns */
.dropdown-menu {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.dropdown-item:hover {
background: rgba(var(--bs-primary-rgb), 0.1);
}
/* Utilities */
.text-primary {
color: var(--bs-primary) !important;
}
.bg-primary {
background-color: var(--bs-primary) !important;
}
.border-primary {
border-color: var(--bs-primary) !important;
}
@@ -1,3 +1,3 @@
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes->merge(['class' => 'text-primary']) }}>
<path fill="currentColor" d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

+86 -10
View File
@@ -1,17 +1,93 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>
@extends('layouts.app')
@section('title', 'Dashboard - Web Proxy Scanner')
@section('description', 'Access your proxy scanning tools, view recent scans, and manage your account settings from your personalized dashboard.')
@section('content')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
{{ __("You're logged in!") }}
<div class="bg-white/80 dark:bg-gray-800/80 backdrop-blur-lg overflow-hidden shadow-xl sm:rounded-lg">
<div class="p-6 lg:p-8">
<div class="flex items-center">
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">
Welcome back, {{ Auth::user()->name }}!
</h1>
</div>
<div class="mt-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<!-- Quick Actions Card -->
<div class="bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 shadow-sm ring-1 ring-gray-900/5 dark:ring-gray-700">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Quick Actions</h2>
<div class="mt-4 space-y-4">
<a href="{{ route('proxies.index') }}" class="flex items-center space-x-3 text-gray-600 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 2a8 8 0 100 16 8 8 0 000-16zm0 2a6 6 0 100 12 6 6 0 000-12zm0 2a4 4 0 100 8 4 4 0 000-8zm0 2a2 2 0 100 4 2 2 0 000-4z" clip-rule="evenodd" />
</svg>
<span>Scan Free Proxies</span>
</a>
<a href="{{ route('port-scanner.index') }}" class="flex items-center space-x-3 text-gray-600 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 2a8 8 0 100 16 8 8 0 000-16zm0 2a6 6 0 100 12 6 6 0 000-12zm0 2a4 4 0 100 8 4 4 0 000-8zm0 2a2 2 0 100 4 2 2 0 000-4z" clip-rule="evenodd" />
</svg>
<span>Port Scanner</span>
</a>
<a href="{{ route('subscriptions.index') }}" class="flex items-center space-x-3 text-gray-600 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 2a8 8 0 100 16 8 8 0 000-16zm0 2a6 6 0 100 12 6 6 0 000-12zm0 2a4 4 0 100 8 4 4 0 000-8zm0 2a2 2 0 100 4 2 2 0 000-4z" clip-rule="evenodd" />
</svg>
<span>Manage Subscription</span>
</a>
</div>
</div>
<!-- Recent Activity Card -->
<div class="bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 shadow-sm ring-1 ring-gray-900/5 dark:ring-gray-700">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Recent Activity</h2>
<div class="mt-4 space-y-4">
<div class="text-sm text-gray-600 dark:text-gray-400">
<p>No recent activity to display.</p>
</div>
</div>
</div>
<!-- Account Status Card -->
<div class="bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 shadow-sm ring-1 ring-gray-900/5 dark:ring-gray-700">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Account Status</h2>
<div class="mt-4 space-y-4">
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Subscription Status</span>
<span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:text-green-200">
Free
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Member Since</span>
<span class="text-sm text-gray-900 dark:text-white">{{ Auth::user()->created_at->format('M d, Y') }}</span>
</div>
</div>
</div>
</div>
</x-app-layout>
<!-- Getting Started Section -->
<div class="mt-8">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Getting Started</h2>
<div class="mt-4 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<div class="bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 shadow-sm ring-1 ring-gray-900/5 dark:ring-gray-700">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">1. Scan Free Proxies</h3>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Start by scanning our list of free proxies to find available servers.</p>
</div>
<div class="bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 shadow-sm ring-1 ring-gray-900/5 dark:ring-gray-700">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">2. Use Port Scanner</h3>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Check port availability and status with our port scanning tool.</p>
</div>
<div class="bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 shadow-sm ring-1 ring-gray-900/5 dark:ring-gray-700">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">3. Upgrade Plan</h3>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Get access to premium features by upgrading your subscription.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
+90 -12
View File
@@ -5,32 +5,110 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<title>@yield('title', config('app.name'))</title>
<meta name="description" content="@yield('description', 'Access our suite of free proxy tools including web proxy scanner, port scanner, and proxy testing.')">
<meta name="keywords" content="proxy scanner, port scanner, proxy testing, network tools, free proxy tools">
<meta name="author" content="{{ config('app.name') }}">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="{{ url()->current() }}">
<meta property="og:title" content="@yield('title', config('app.name'))">
<meta property="og:description" content="@yield('description', 'Access our suite of free proxy tools including web proxy scanner, port scanner, and proxy testing.')">
<meta property="og:image" content="{{ asset('images/og-image.jpg') }}">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="{{ url()->current() }}">
<meta property="twitter:title" content="@yield('title', config('app.name'))">
<meta property="twitter:description" content="@yield('description', 'Access our suite of free proxy tools including web proxy scanner, port scanner, and proxy testing.')">
<meta property="twitter:image" content="{{ asset('images/og-image.jpg') }}">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<!-- Custom CSS -->
<style>
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
}
.dark body {
background: linear-gradient(135deg, #1a1c2e 0%, #2d3748 100%);
}
.nav-link {
position: relative;
transition: color 0.3s ease;
}
.nav-link::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: -2px;
left: 0;
background-color: currentColor;
transition: width 0.3s ease;
}
.nav-link:hover::after {
width: 100%;
}
.btn-primary {
background: linear-gradient(135deg, #0d6efd 0%, #0a58ca 100%);
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3);
}
.card {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.dark .card {
background: rgba(31, 41, 55, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
<body>
<div class="min-vh-100">
@include('layouts.navigation')
<!-- Page Heading -->
@isset($header)
<header class="bg-white dark:bg-gray-800 shadow">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
@if (isset($header))
<header class="bg-white bg-opacity-80 shadow-sm">
<div class="container py-3">
{{ $header }}
</div>
</header>
@endisset
@endif
<!-- Page Content -->
<main>
{{ $slot }}
@yield('content')
</main>
</div>
<!-- Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom Scripts -->
@vite(['resources/js/app.js'])
</body>
</html>
+55 -112
View File
@@ -1,132 +1,75 @@
<nav x-data="{ open: false }" class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard') }}">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200" />
<a class="navbar-brand" href="{{ route('dashboard') }}">
<x-application-logo class="h-9 w-auto" />
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
<x-nav-link :href="route('proxies.index')" :active="request()->routeIs('proxies.index')">
{{ __('Free Proxies') }}
</x-nav-link>
<x-nav-link :href="route('subscriptions.index')" :active="request()->routeIs('subscriptions.index')">
{{ __('Subscriptions') }}
</x-nav-link>
</div>
</div>
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ms-6">
@auth
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div class="ms-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-dropdown-link>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-dropdown-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
</x-slot>
</x-dropdown>
@else
<div class="space-x-4">
<a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 hover:text-gray-900 dark:hover:text-gray-300">Login</a>
<a href="{{ route('register') }}" class="text-sm text-gray-700 dark:text-gray-500 hover:text-gray-900 dark:hover:text-gray-300">Register</a>
</div>
@endauth
</div>
<!-- Hamburger -->
<div class="-me-2 flex items-center sm:hidden">
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
</div>
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
<!-- Navigation Content -->
<div class="collapse navbar-collapse" id="navbarNav">
<!-- Navigation Links -->
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}" href="{{ route('dashboard') }}">
{{ __('Dashboard') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('proxies.index')" :active="request()->routeIs('proxies.index')">
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('proxies.index') ? 'active' : '' }}" href="{{ route('proxies.index') }}">
{{ __('Free Proxies') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('subscriptions.index')" :active="request()->routeIs('subscriptions.index')">
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('port-scanner.index') ? 'active' : '' }}" href="{{ route('port-scanner.index') }}">
{{ __('Port Scanner') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('subscriptions.index') ? 'active' : '' }}" href="{{ route('subscriptions.index') }}">
{{ __('Subscriptions') }}
</x-responsive-nav-link>
</div>
</a>
</li>
</ul>
<!-- Responsive Settings Options -->
<!-- Settings Dropdown -->
<ul class="navbar-nav">
@auth
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600">
<div class="px-4">
<div class="font-medium text-base text-gray-800 dark:text-gray-200">{{ Auth::user()->name }}</div>
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
<div class="mt-3 space-y-1">
<x-responsive-nav-link :href="route('profile.edit')">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ Auth::user()->name }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{{ route('profile.edit') }}">
{{ __('Profile') }}
</x-responsive-nav-link>
<!-- Authentication -->
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-responsive-nav-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault(); this.closest('form').submit();">
{{ __('Log Out') }}
</x-responsive-nav-link>
</a>
</form>
</div>
</div>
</li>
</ul>
</li>
@else
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600">
<div class="mt-3 space-y-1">
<x-responsive-nav-link :href="route('login')">
{{ __('Login') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('register')">
{{ __('Register') }}
</x-responsive-nav-link>
</div>
</div>
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endauth
</ul>
</div>
</div>
</nav>
+127 -42
View File
@@ -3,76 +3,161 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web Proxy - @yield('title')</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="antialiased">
<div class="min-h-screen bg-gray-100">
<!-- Navigation Bar -->
<nav class="bg-white shadow-lg">
<div class="max-w-7xl mx-auto px-4">
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
<!-- Navigation -->
<nav class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="flex-shrink-0 flex items-center">
<a href="{{ route('proxy.home') }}" class="text-xl font-bold text-gray-800">
WebProxyScan
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard') }}">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200" />
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<a href="{{ route('dashboard') }}" class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900">
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</a>
<a href="{{ route('proxies.index') }}" class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900">
</x-nav-link>
<x-nav-link :href="route('proxies.index')" :active="request()->routeIs('proxies.index')">
{{ __('Free Proxies') }}
</a>
<a href="" class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900">
{{ __('PAC File') }}
</a>
<a href="{{ route('subscriptions.index') }}" class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900">
</x-nav-link>
<x-nav-link :href="route('port-scanner.index')" :active="request()->routeIs('port-scanner.index')">
{{ __('Port Scanner') }}
</x-nav-link>
<x-nav-link :href="route('subscriptions.index')" :active="request()->routeIs('subscriptions.index')">
{{ __('Subscriptions') }}
</a>
</x-nav-link>
</div>
</div>
<!-- Right Navigation -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ms-6">
@auth
<div class="relative" x-data="{ open: false }">
<button @click="open = !open" class="flex items-center text-sm font-medium text-gray-900 hover:text-gray-700 focus:outline-none">
<span>{{ Auth::user()->name }}</span>
<svg class="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div class="ms-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
<div x-show="open" @click.away="open = false" class="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5">
<div class="py-1">
<a href="{{ route('profile.edit') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Profile</a>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-dropdown-link>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Log Out
</button>
<x-dropdown-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
</div>
</div>
</div>
</x-slot>
</x-dropdown>
@else
<div class="space-x-4">
<a href="{{ route('login') }}" class="text-sm text-gray-700 hover:text-gray-900">Login</a>
<a href="{{ route('register') }}" class="text-sm text-gray-700 hover:text-gray-900">Register</a>
<a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 hover:text-gray-900 dark:hover:text-gray-300">Login</a>
<a href="{{ route('register') }}" class="text-sm text-gray-700 dark:text-gray-500 hover:text-gray-900 dark:hover:text-gray-300">Register</a>
</div>
@endauth
</div>
<!-- Hamburger -->
<div class="-me-2 flex items-center sm:hidden">
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('proxies.index')" :active="request()->routeIs('proxies.index')">
{{ __('Free Proxies') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('port-scanner.index')" :active="request()->routeIs('port-scanner.index')">
{{ __('Port Scanner') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('subscriptions.index')" :active="request()->routeIs('subscriptions.index')">
{{ __('Subscriptions') }}
</x-responsive-nav-link>
</div>
<!-- Responsive Settings Options -->
@auth
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600">
<div class="px-4">
<div class="font-medium text-base text-gray-800 dark:text-gray-200">{{ Auth::user()->name }}</div>
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
<div class="mt-3 space-y-1">
<x-responsive-nav-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-responsive-nav-link>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-responsive-nav-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-responsive-nav-link>
</form>
</div>
</div>
@else
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600">
<div class="mt-3 space-y-1">
<x-responsive-nav-link :href="route('login')">
{{ __('Login') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('register')">
{{ __('Register') }}
</x-responsive-nav-link>
</div>
</div>
@endauth
</div>
</nav>
<!-- Main Content -->
<main class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
@yield('content')
</div>
<!-- Page Content -->
<main>
{{ $slot }}
</main>
</div>
</body>
@@ -0,0 +1,167 @@
@extends('layouts.app')
@section('content')
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-body p-4">
<div class="text-center mb-5">
<h2 class="display-5 fw-bold mb-3">Port Scanner</h2>
<p class="lead text-muted">Check if a port is open on any IP address</p>
</div>
<div class="mb-5">
<form id="portScanForm" class="needs-validation" novalidate>
<div class="row g-4">
<div class="col-md-6">
<div class="form-floating">
<input type="text" class="form-control" id="ip" name="ip" required
placeholder="IP Address">
<label for="ip">IP Address</label>
<div class="invalid-feedback">
Please enter a valid IP address
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
<input type="number" class="form-control" id="port" name="port" required
min="1" max="65535" placeholder="Port">
<label for="port">Port</label>
<div class="invalid-feedback">
Please enter a valid port number (1-65535)
</div>
</div>
</div>
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-primary btn-lg px-5">
<i class="bi bi-search me-2"></i>Scan Port
</button>
</div>
</form>
</div>
<div id="result" class="d-none">
<div class="card bg-light border-0">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<i id="statusIcon" class="bi bi-circle-fill fs-1"></i>
</div>
<div class="flex-grow-1 ms-3">
<h3 id="resultTitle" class="h5 mb-2"></h3>
<div id="resultDetails" class="text-muted"></div>
</div>
</div>
</div>
</div>
</div>
<div id="loading" class="d-none">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('portScanForm');
const result = document.getElementById('result');
const loading = document.getElementById('loading');
const statusIcon = document.getElementById('statusIcon');
const resultTitle = document.getElementById('resultTitle');
const resultDetails = document.getElementById('resultDetails');
// Form validation
form.addEventListener('submit', async function(e) {
e.preventDefault();
if (!form.checkValidity()) {
e.stopPropagation();
form.classList.add('was-validated');
return;
}
// Show loading, hide result
loading.classList.remove('d-none');
result.classList.add('d-none');
try {
const response = await fetch('/port-scanner/scan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
ip: document.getElementById('ip').value,
port: document.getElementById('port').value
})
});
const data = await response.json();
// Set icon based on status
if (data.status === 'open') {
statusIcon.className = 'bi bi-check-circle-fill text-success fs-1';
} else {
statusIcon.className = 'bi bi-x-circle-fill text-danger fs-1';
}
// Set result content
resultTitle.textContent = data.message;
resultDetails.innerHTML = `
<div class="row g-3">
<div class="col-md-4">
<div class="p-2 bg-white rounded">
<small class="d-block text-muted">Response Time</small>
<strong>${data.response_time}ms</strong>
</div>
</div>
<div class="col-md-4">
<div class="p-2 bg-white rounded">
<small class="d-block text-muted">IP Address</small>
<strong>${data.ip}</strong>
</div>
</div>
<div class="col-md-4">
<div class="p-2 bg-white rounded">
<small class="d-block text-muted">Port</small>
<strong>${data.port}</strong>
</div>
</div>
</div>
`;
// Show result, hide loading
result.classList.remove('d-none');
loading.classList.add('d-none');
} catch (error) {
// Show error
statusIcon.className = 'bi bi-exclamation-circle-fill text-danger fs-1';
resultTitle.textContent = 'Error';
resultDetails.innerHTML = `
<div class="alert alert-danger mb-0">
An error occurred while scanning the port. Please try again.
</div>
`;
// Show result, hide loading
result.classList.remove('d-none');
loading.classList.add('d-none');
}
});
});
</script>
@endpush
@endsection
+145 -13
View File
@@ -1,18 +1,150 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Free Proxies') }}
</h2>
</x-slot>
@extends('layouts.app')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<h3 class="text-lg font-medium mb-4">Available Free Proxies</h3>
<p class="text-gray-600">This page will display a list of free proxies. Content coming soon.</p>
@section('content')
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="h4 mb-1">Free Proxies</h2>
<p class="text-muted mb-0">List of available proxies sorted by reliability score</p>
</div>
<div class="text-end">
<span class="badge bg-primary">{{ $proxies->total() }} proxies</span>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Proxy</th>
<th>Protocol</th>
<th>Location</th>
<th>Anonymity</th>
<th>HTTPS</th>
<th>Score</th>
<th>Last Tested</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($proxies as $proxy)
<tr>
<td>
<div class="d-flex align-items-center">
<i class="bi bi-globe me-2"></i>
<div>
<div class="fw-bold">{{ $proxy->ip }}</div>
<small class="text-muted">Port: {{ $proxy->port }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-secondary">{{ strtoupper($proxy->protocol) }}</span>
</td>
<td>
<div class="d-flex align-items-center">
<i class="bi bi-geo-alt me-2"></i>
<div>
<div>{{ $proxy->city }}</div>
<small class="text-muted">{{ $proxy->country }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-info">{{ ucfirst($proxy->anonymity) }}</span>
</td>
<td>
@if($proxy->https)
<span class="badge bg-success">Yes</span>
@else
<span class="badge bg-danger">No</span>
@endif
</td>
<td>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1 me-2" style="height: 6px;">
<div class="progress-bar {{ $proxy->score >= 80 ? 'bg-success' : ($proxy->score >= 50 ? 'bg-warning' : 'bg-danger') }}"
role="progressbar"
style="width: {{ $proxy->score }}%"
aria-valuenow="{{ $proxy->score }}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="fw-bold">{{ $proxy->score }}%</span>
</div>
</td>
<td>
<div class="d-flex align-items-center">
<i class="bi bi-clock me-2"></i>
<div>
@if($proxy->last_tested_at)
<div>{{ $proxy->last_tested_at->diffForHumans() }}</div>
<small class="text-muted">{{ $proxy->last_tested_at->format('M d, Y H:i') }}</small>
@else
<div class="text-muted">Never tested</div>
<small class="text-muted">-</small>
@endif
</div>
</div>
</td>
<td>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="copyToClipboard('{{ $proxy->proxy }}')">
<i class="bi bi-clipboard"></i>
</button>
<a href="http://{{ $proxy->proxy }}" target="_blank"
class="btn btn-sm btn-outline-secondary">
<i class="bi bi-box-arrow-up-right"></i>
</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center py-4">
<div class="text-muted">
<i class="bi bi-inbox me-2"></i>No proxies available at the moment
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="d-flex justify-content-center mt-4">
{{ $proxies->links('vendor.pagination.bootstrap-5') }}
</div>
</div>
</div>
</div>
</x-app-layout>
</div>
</div>
@push('scripts')
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
// Show a temporary success message
const btn = event.currentTarget;
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class="bi bi-check"></i>';
btn.classList.remove('btn-outline-primary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalContent;
btn.classList.remove('btn-success');
btn.classList.add('btn-outline-primary');
}, 2000);
});
}
</script>
@endpush
@endsection
-25
View File
@@ -1,25 +0,0 @@
@extends('layouts.proxy')
@section('title', 'Browsing: ' . $url)
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<div class="mb-4">
<form action="{{ route('proxy.browse') }}" method="POST" class="flex space-x-2">
@csrf
<input type="url" name="url" value="{{ htmlspecialchars($url) }}"
class="flex-1 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
<button type="submit"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Go
</button>
</form>
</div>
<div class="prose max-w-none">
{!! $content !!}
</div>
</div>
</div>
@endsection
+53
View File
@@ -0,0 +1,53 @@
@if ($paginator->hasPages())
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled">
<span class="page-link">
<i class="bi bi-chevron-left"></i>
</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">
<i class="bi bi-chevron-left"></i>
</a>
</li>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="page-item active">
<span class="page-link">{{ $page }}</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $url }}">{{ $page }}</a>
</li>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">
<i class="bi bi-chevron-right"></i>
</a>
</li>
@else
<li class="page-item disabled">
<span class="page-link">
<i class="bi bi-chevron-right"></i>
</span>
</li>
@endif
</ul>
</nav>
@endif
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -4,6 +4,7 @@ use App\Http\Controllers\ProfileController;
use App\Http\Controllers\ProxyController;
use App\Http\Controllers\ProxyListController;
use App\Http\Controllers\SubscriptionController;
use App\Http\Controllers\PortScannerController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
@@ -28,14 +29,13 @@ Route::get('/proxy-test', function () {
]);
});
// Proxy routes
Route::get('/', [ProxyController::class, 'home'])->name('proxy.home');
Route::match(['get', 'post'], '/browse', [ProxyController::class, 'browse'])->name('proxy.browse');
// Free Proxies routes
Route::get('/proxies', [ProxyListController::class, 'index'])->name('proxies.index');
// Subscription routes
Route::get('/subscriptions', [SubscriptionController::class, 'index'])->name('subscriptions.index');
Route::get('/port-scanner', [PortScannerController::class, 'index'])->name('port-scanner.index');
Route::post('/port-scanner/scan', [PortScannerController::class, 'scan'])->name('port-scanner.scan');
require __DIR__.'/auth.php';
+1 -1
View File
@@ -4,7 +4,7 @@ import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
input: ['resources/js/app.js'],
refresh: true,
}),
],