🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@axiosleo/koapp

Package Overview
Dependencies
Maintainers
1
Versions
78
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@axiosleo/koapp - npm Package Compare versions

Comparing version
1.1.0-alpha.2
to
1.1.0
+425
assets/html/404.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404 - 页面未找到 | Page Not Found</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue",
Helvetica, Arial, sans-serif;
background: #f5f5f5;
min-height: 100vh;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.container {
max-width: 800px;
width: 100%;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
text-align: center;
}
.header {
background: linear-gradient(45deg, #ff6b6b, #ffa726);
color: white;
padding: 50px 30px;
}
.error-code {
font-size: 8rem;
font-weight: 300;
margin-bottom: 20px;
opacity: 0.9;
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.error-title {
font-size: 2.5rem;
margin-bottom: 15px;
font-weight: 300;
}
.error-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin-bottom: 10px;
}
.error-description {
font-size: 1rem;
opacity: 0.8;
}
.content {
padding: 50px 30px;
}
.error-icon {
font-size: 6rem;
margin-bottom: 30px;
opacity: 0.6;
}
.error-message {
font-size: 1.2rem;
color: #666;
margin-bottom: 30px;
line-height: 1.6;
}
.suggestions {
text-align: left;
background: #f8f9fa;
border-radius: 8px;
padding: 30px;
margin: 30px 0;
}
.suggestions h3 {
font-size: 1.3rem;
color: #333;
margin-bottom: 20px;
display: flex;
align-items: center;
}
.suggestions h3::before {
content: "💡";
margin-right: 10px;
font-size: 1.5rem;
}
.suggestions ul {
list-style: none;
padding: 0;
}
.suggestions li {
padding: 10px 0;
color: #666;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
}
.suggestions li:last-child {
border-bottom: none;
}
.suggestions li::before {
content: "→";
color: #2196f3;
font-weight: bold;
margin-right: 10px;
}
.action-buttons {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
margin-top: 30px;
}
.btn {
padding: 15px 30px;
border: none;
border-radius: 25px;
font-size: 16px;
font-weight: 500;
text-decoration: none;
transition: all 0.3s;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(45deg, #2196f3, #21cbf3);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(33, 150, 243, 0.3);
}
.btn-secondary {
background: #f5f5f5;
color: #666;
border: 2px solid #e0e0e0;
}
.btn-secondary:hover {
background: #e0e0e0;
border-color: #ccc;
transform: translateY(-2px);
}
.stats {
display: flex;
justify-content: space-around;
padding: 20px;
background: #f8f9fa;
border-top: 1px solid #e0e0e0;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #ff6b6b;
display: block;
}
.stat-label {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.breadcrumb {
padding: 15px 30px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
font-size: 14px;
color: #666;
text-align: left;
}
.breadcrumb a {
color: #2196f3;
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.container {
margin: 10px;
border-radius: 5px;
}
.header {
padding: 30px 20px;
}
.error-code {
font-size: 5rem;
}
.error-title {
font-size: 2rem;
}
.content {
padding: 30px 20px;
}
.error-icon {
font-size: 4rem;
}
.action-buttons {
flex-direction: column;
align-items: center;
}
.btn {
width: 100%;
max-width: 300px;
}
.stats {
flex-direction: column;
gap: 15px;
}
}
/* 动画效果 */
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
.error-icon {
animation: float 3s ease-in-out infinite;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
</style>
</head>
<body>
<div class="container">
<!-- <div class="breadcrumb">
<a href="/" onclick="goHome()">🏠 首页</a> /
<a href="javascript:history.back()">📄 上一页</a> /
<span>404 错误</span>
</div> -->
<!-- <div class="header">
<div class="error-code fade-in-up">404</div>
<h1 class="error-title fade-in-up">页面未找到</h1>
<p class="error-subtitle fade-in-up">Page Not Found</p>
<p class="error-description fade-in-up">
抱歉,您访问的页面不存在或已被移动
</p>
</div> -->
<div class="content">
<div class="error-icon">🔍</div>
<div class="error-message fade-in-up">
很抱歉,无法找到您要访问的页面。<br />
这可能是因为页面已被删除、移动或者您输入的网址有误。
</div>
<div class="suggestions fade-in-up">
<h3>您可以尝试以下操作</h3>
<ul>
<li>检查网址是否输入正确</li>
<li>返回上一页继续浏览</li>
<li>访问网站首页</li>
<li>使用搜索功能查找内容</li>
<li>联系网站管理员报告此问题</li>
</ul>
</div>
<div class="action-buttons fade-in-up">
<a href="/" class="btn btn-primary" onclick="goHome()">
🏠 返回首页
</a>
<a href="javascript:history.back()" class="btn btn-secondary">
← 返回上页
</a>
<!-- <a href="javascript:location.reload()" class="btn btn-secondary">
🔄 刷新页面
</a> -->
</div>
</div>
<!-- <div class="stats">
<div class="stat-item">
<span class="stat-number">404</span>
<div class="stat-label">错误代码</div>
</div>
<div class="stat-item">
<span class="stat-number" id="currentTime">--:--</span>
<div class="stat-label">当前时间</div>
</div>
<div class="stat-item">
<span class="stat-number" id="stayTime">0</span>
<div class="stat-label">停留时间(秒)</div>
</div>
</div> -->
</div>
<script>
// 返回首页功能
function goHome() {
// 尝试多种方式返回首页
if (window.location.origin) {
window.location.href = window.location.origin;
} else {
window.location.href = "/";
}
}
// 更新当前时间
function updateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString("zh-CN", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
});
document.getElementById("currentTime").textContent = timeString;
}
// 计算停留时间
let staySeconds = 0;
function updateStayTime() {
document.getElementById("stayTime").textContent = staySeconds;
staySeconds++;
}
// 初始化
updateTime();
setInterval(updateTime, 1000);
setInterval(updateStayTime, 1000);
// 添加加载动画
window.addEventListener("load", function () {
const fadeElements = document.querySelectorAll(".fade-in-up");
fadeElements.forEach((element, index) => {
element.style.opacity = "0";
element.style.transform = "translateY(30px)";
setTimeout(() => {
element.style.transition = "all 0.6s ease-out";
element.style.opacity = "1";
element.style.transform = "translateY(0)";
}, index * 100);
});
});
// 键盘快捷键
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
history.back();
} else if (e.key === "Home" || (e.ctrlKey && e.key === "h")) {
e.preventDefault();
goHome();
} else if (e.key === "F5" || (e.ctrlKey && e.key === "r")) {
// 允许默认的刷新行为
}
});
// 记录访问信息(可选)
console.log("404 页面访问信息:", {
url: window.location.href,
referrer: document.referrer,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
});
</script>
</body>
</html>
+1
-1

@@ -22,3 +22,3 @@ {

"dependencies": {
"@axiosleo/koapp": "^1.1.0-alpha.2",
"@axiosleo/koapp": "^1.1.0",
"@axiosleo/orm-mysql": "^0.11.2",

@@ -25,0 +25,0 @@ "cluster": "^0.7.7",

@@ -12,3 +12,3 @@ #!/usr/bin/env node

bin: 'koapp',
version: '1.1.0-alpha.2',
version: '1.1.0',
commands_dir: path.join(__dirname, '../commands'),

@@ -15,0 +15,0 @@ });

@@ -5,6 +5,7 @@ 'use strict';

const { Command, printer } = require('@axiosleo/cli-tool');
const { error, Router, KoaApplication, result } = require('../');
const { Router, KoaApplication, result } = require('../');
const path = require('path');
const promisify = require('util').promisify;
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);
const {

@@ -16,3 +17,5 @@ _exists,

} = require('@axiosleo/cli-tool/src/helper/fs');
const { _fixed } = require('@axiosleo/cli-tool/src/helper/str');
const {
_fixed
} = require('@axiosleo/cli-tool/src/helper/str');

@@ -48,4 +51,34 @@ const mimeTypes = {

this.addOption('port', 'p', 'Port', 'optional', 80);
this.html404Content = null;
}
formatFileSize(bytes) {
if (bytes === 0) { return '--'; }
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
getFileType(filename, is_dir, ext) {
if (is_dir) { return 'folder'; }
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'];
const codeExts = ['js', 'ts', 'html', 'css', 'json', 'xml', 'sh', 'py', 'java', 'cpp', 'c', 'php'];
if (imageExts.includes(ext.toLowerCase())) { return 'image'; }
if (codeExts.includes(ext.toLowerCase())) { return 'code'; }
return 'document';
}
getFileIcon(type) {
const icons = {
folder: '📁',
document: '📄',
image: '🖼️',
code: '💻'
};
return icons[type] || '📄';
}
async list(dir) {

@@ -63,4 +96,17 @@ if (!await _exists(dir)) {

let is_dir = await _is_dir(filepath);
files.push({ filename, is_dir, filepath });
let stats = await stat(filepath);
let size = is_dir ? 0 : stats.size;
let mtime = stats.mtime;
let ext = is_dir ? '' : _ext(filename);
files.push({
filename,
is_dir,
filepath,
size,
mtime,
ext
});
}));
// debug.log(files);
return files;

@@ -82,3 +128,39 @@ }

if (!await _exists(d)) {
error(404, 'Not Found');
if (!this.html404Content) {
// 读取 404.html 文件内容
const html404Path = path.join(__dirname, '../assets/html/404.html');
let html404Content = 'Not Found';
try {
html404Content = fs.readFileSync(html404Path, 'utf8');
} catch (_error) { // eslint-disable-line no-unused-vars
// 如果读取失败,使用简单的 HTML 404 页面
html404Content = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - 页面未找到</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
.container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
h1 { color: #ff6b6b; font-size: 4rem; margin: 0; }
h2 { color: #333; margin: 20px 0; }
p { color: #666; line-height: 1.6; }
.btn { display: inline-block; background: #2196f3; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin-top: 20px; }
</style>
</head>
<body>
<div class="container">
<h1>404</h1>
<h2>页面未找到</h2>
<p>抱歉,您访问的页面不存在或已被移动。</p>
<a href="/" class="btn">返回首页</a>
</div>
</body>
</html>`;
}
this.html404Content = html404Content;
}
result(this.html404Content, 404, { 'Content-Type': 'text/html' });
}

@@ -99,13 +181,393 @@ if (await _is_file(d)) {

}
let htmlContent = '<ul><li><a href="../">../</a></li>';
let files = await this.list(d);
files = files.sort((a, b) => {
// Directories first, then files, both alphabetically
if (a.is_dir && !b.is_dir) { return -1; }
if (!a.is_dir && b.is_dir) { return 1; }
return a.filename.toLowerCase() < b.filename.toLowerCase() ? -1 : 1;
});
// Calculate statistics
const folderCount = files.filter(f => f.is_dir).length;
const fileCount = files.filter(f => !f.is_dir).length;
const totalSize = files.reduce((sum, f) => sum + (f.size || 0), 0);
// Generate breadcrumb
const pathParts = url.pathname.split('/').filter(p => p);
let breadcrumb = '<a href="/">🏠 Home</a>';
let currentPath = '';
pathParts.forEach(part => {
currentPath += '/' + part;
breadcrumb += ` / <a href="${currentPath}">${decodeURIComponent(part)}</a>`;
});
// Generate file items HTML
let fileItemsHTML = '';
// Add parent directory link if not at root
if (url.pathname !== '/') {
fileItemsHTML += `
<div class="file-item" data-type="folder" onclick="window.location.href='../'">
<div class="file-icon folder">📁</div>
<div class="file-info">
<div class="file-name">../</div>
<div class="file-meta">返回上级目录</div>
</div>
<div class="file-size">--</div>
</div>`;
}
files.forEach(f => {
let p = url.pathname === '/' ? '.' : url.pathname;
p = p + '/' + f.filename;
htmlContent += `<li><a href="${p}">${f.filename}</a></li>`;
const fileType = this.getFileType(f.filename, f.is_dir, f.ext);
const fileIcon = this.getFileIcon(fileType);
const fileSize = this.formatFileSize(f.size);
const modifiedDate = f.mtime.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
const fileTypeText = f.is_dir ? '文件夹' : (f.ext ? f.ext.toUpperCase() + '文件' : '文件');
fileItemsHTML += `
<div class="file-item" data-type="${fileType}" onclick="window.location.href='${p}'">
<div class="file-icon ${fileType}">${fileIcon}</div>
<div class="file-info">
<div class="file-name">${f.filename}</div>
<div class="file-meta">${fileTypeText} • 最后修改: ${modifiedDate}</div>
</div>
<div class="file-size">${fileSize}</div>
</div>`;
});
htmlContent += '</ul>';
const htmlContent = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文件列表 - File List</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue",
Helvetica, Arial, sans-serif;
background: #f5f5f5;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.header {
background: linear-gradient(45deg, #2196f3, #21cbf3);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
font-weight: 300;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.file-list {
padding: 30px;
}
.search-box {
margin-bottom: 30px;
}
.search-box input {
width: 100%;
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 25px;
font-size: 16px;
transition: border-color 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #2196f3;
}
.file-item {
display: flex;
align-items: center;
padding: 15px 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 10px;
transition: all 0.3s;
cursor: pointer;
}
.file-item:hover {
background: #f5f5f5;
border-color: #2196f3;
transform: translateX(5px);
}
.file-icon {
width: 40px;
height: 40px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
font-size: 18px;
color: white;
}
.file-icon.folder {
background: #ffa726;
}
.file-icon.document {
background: #42a5f5;
}
.file-icon.image {
background: #66bb6a;
}
.file-icon.code {
background: #ab47bc;
}
.file-info {
flex: 1;
}
.file-name {
font-size: 16px;
font-weight: 500;
margin-bottom: 5px;
color: #333;
}
.file-meta {
font-size: 14px;
color: #666;
}
.file-size {
font-size: 14px;
color: #999;
min-width: 80px;
text-align: right;
}
.stats {
display: flex;
justify-content: space-around;
padding: 20px;
background: #f8f9fa;
border-top: 1px solid #e0e0e0;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #2196f3;
display: block;
}
.stat-label {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.breadcrumb {
padding: 15px 30px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
font-size: 14px;
color: #666;
}
.breadcrumb a {
color: #2196f3;
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.container {
margin: 10px;
border-radius: 5px;
}
.header {
padding: 20px;
}
.header h1 {
font-size: 2rem;
}
.file-list {
padding: 20px;
}
.file-item {
padding: 10px 15px;
}
.stats {
flex-direction: column;
gap: 15px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📁 文件管理器</h1>
<p>File Manager</p>
</div>
<div class="breadcrumb">
${breadcrumb}
</div>
<div class="file-list">
<div class="search-box">
<input
type="text"
placeholder="🔍 在当前目录下搜索..."
id="searchInput"
/>
</div>
<div id="fileContainer">
${fileItemsHTML}
</div>
</div>
<div class="stats">
<div class="stat-item">
<span class="stat-number">${folderCount}</span>
<div class="stat-label">文件夹</div>
</div>
<div class="stat-item">
<span class="stat-number">${fileCount}</span>
<div class="stat-label">文件</div>
</div>
<div class="stat-item">
<span class="stat-number">${this.formatFileSize(totalSize)}</span>
<div class="stat-label">总大小</div>
</div>
</div>
</div>
<script>
// 搜索功能
const searchInput = document.getElementById("searchInput");
const fileContainer = document.getElementById("fileContainer");
const fileItems = document.querySelectorAll(".file-item");
searchInput.addEventListener("input", function () {
const searchTerm = this.value.toLowerCase();
fileItems.forEach((item) => {
const fileName = item
.querySelector(".file-name")
.textContent.toLowerCase();
const fileMeta = item
.querySelector(".file-meta")
.textContent.toLowerCase();
if (fileName.includes(searchTerm) || fileMeta.includes(searchTerm)) {
item.style.display = "flex";
} else {
item.style.display = "none";
}
});
// 更新统计信息
updateStats();
});
// 更新统计信息
function updateStats() {
const visibleItems = Array.from(fileItems).filter(
(item) => item.style.display !== "none"
);
const folders = visibleItems.filter(
(item) => item.getAttribute("data-type") === "folder"
).length;
const files = visibleItems.length - folders;
document.querySelector(
".stats .stat-item:nth-child(1) .stat-number"
).textContent = folders;
document.querySelector(
".stats .stat-item:nth-child(2) .stat-number"
).textContent = files;
}
// 键盘快捷键
document.addEventListener("keydown", function (e) {
if (e.ctrlKey || e.metaKey) {
if (e.key === "f") {
e.preventDefault();
searchInput.focus();
}
}
});
// 添加加载动画
window.addEventListener("load", function () {
fileItems.forEach((item, index) => {
item.style.opacity = "0";
item.style.transform = "translateY(20px)";
setTimeout(() => {
item.style.transition = "all 0.3s ease";
item.style.opacity = "1";
item.style.transform = "translateY(0)";
}, index * 50);
});
});
</script>
</body>
</html>`;
// debug.log('Generated HTML file listing');
result(htmlContent, 200, { 'Content-Type': 'text/html' });

@@ -112,0 +574,0 @@ }],

{
"name": "@axiosleo/koapp",
"version": "1.1.0-alpha.2",
"version": "1.1.0",
"description": "",

@@ -5,0 +5,0 @@ "author": "AxiosLeo",