@lppx/lanshare 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -0
- package/dist/scripts/gitsync.js +100 -0
- package/dist/src/app.js +46 -0
- package/dist/src/cli/cli.js +131 -0
- package/dist/src/cli/index.js +30 -0
- package/dist/src/cli/network.js +29 -0
- package/dist/src/cli.js +19 -0
- package/dist/src/index.js +26 -0
- package/dist/src/routers/files.js +142 -0
- package/dist/src/routers/upload.js +47 -0
- package/dist/src/socket/index.js +65 -0
- package/dist/src/socket/types.js +2 -0
- package/dist/src/utils/logger.js +48 -0
- package/package.json +45 -0
- package/public/css/base.css +44 -0
- package/public/css/device-list.css +76 -0
- package/public/css/drop-zone.css +39 -0
- package/public/css/file-list.css +167 -0
- package/public/css/header.css +48 -0
- package/public/css/responsive.css +9 -0
- package/public/index.html +92 -0
- package/public/js/api.js +54 -0
- package/public/js/device-list.js +23 -0
- package/public/js/file-list.js +62 -0
- package/public/js/main.js +18 -0
- package/public/js/socket.js +63 -0
- package/public/js/upload.js +49 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createUploadRouter = createUploadRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const multer_1 = __importDefault(require("multer"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const logger_1 = __importDefault(require("../utils/logger"));
|
|
12
|
+
function createUploadRouter(uploadDir) {
|
|
13
|
+
const router = (0, express_1.Router)();
|
|
14
|
+
// #region 配置文件上传
|
|
15
|
+
const storage = multer_1.default.diskStorage({
|
|
16
|
+
destination: (_req, _file, cb) => {
|
|
17
|
+
cb(null, uploadDir);
|
|
18
|
+
},
|
|
19
|
+
filename: (_req, file, cb) => {
|
|
20
|
+
// 保留原始文件名,如果文件已存在则添加数字后缀
|
|
21
|
+
let filename = file.originalname;
|
|
22
|
+
let filepath = path_1.default.join(uploadDir, filename);
|
|
23
|
+
let counter = 1;
|
|
24
|
+
while (fs_1.default.existsSync(filepath)) {
|
|
25
|
+
const ext = path_1.default.extname(file.originalname);
|
|
26
|
+
const basename = path_1.default.basename(file.originalname, ext);
|
|
27
|
+
filename = `${basename} (${counter})${ext}`;
|
|
28
|
+
filepath = path_1.default.join(uploadDir, filename);
|
|
29
|
+
counter++;
|
|
30
|
+
}
|
|
31
|
+
cb(null, filename);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const upload = (0, multer_1.default)({ storage });
|
|
35
|
+
// #endregion
|
|
36
|
+
// #region 上传路由
|
|
37
|
+
router.post('/upload', upload.single('file'), (_req, res) => {
|
|
38
|
+
if (!_req.file) {
|
|
39
|
+
logger_1.default.warn('上传失败: 没有文件');
|
|
40
|
+
return res.status(400).json({ error: '没有文件上传' });
|
|
41
|
+
}
|
|
42
|
+
logger_1.default.info(`文件上传成功: ${_req.file.originalname} -> ${_req.file.filename} (${_req.file.size} bytes)`);
|
|
43
|
+
res.json({ message: '文件上传成功', filename: _req.file.filename });
|
|
44
|
+
});
|
|
45
|
+
// #endregion
|
|
46
|
+
return router;
|
|
47
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setupSocketIO = setupSocketIO;
|
|
7
|
+
const socket_io_1 = require("socket.io");
|
|
8
|
+
const logger_1 = __importDefault(require("../utils/logger"));
|
|
9
|
+
function setupSocketIO(httpServer) {
|
|
10
|
+
const io = new socket_io_1.Server(httpServer, {
|
|
11
|
+
cors: {
|
|
12
|
+
origin: '*',
|
|
13
|
+
methods: ['GET', 'POST']
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const connectedDevices = new Map();
|
|
17
|
+
io.on('connection', (socket) => {
|
|
18
|
+
logger_1.default.info(`新连接: ${socket.id} - ${socket.handshake.address}`);
|
|
19
|
+
// 设备注册
|
|
20
|
+
socket.on('register', (deviceInfo) => {
|
|
21
|
+
const deviceId = deviceInfo.deviceId || socket.id;
|
|
22
|
+
// 检查是否已存在该设备,如果存在则更新socketId
|
|
23
|
+
let existingDevice;
|
|
24
|
+
for (const [key, device] of connectedDevices.entries()) {
|
|
25
|
+
if (device.id === deviceId) {
|
|
26
|
+
existingDevice = device;
|
|
27
|
+
// 删除旧的socket映射
|
|
28
|
+
connectedDevices.delete(key);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const device = {
|
|
33
|
+
id: deviceId,
|
|
34
|
+
socketId: socket.id,
|
|
35
|
+
name: deviceInfo.name || 'Unknown Device',
|
|
36
|
+
ip: socket.handshake.address,
|
|
37
|
+
platform: deviceInfo.platform || 'Unknown',
|
|
38
|
+
connectedAt: existingDevice?.connectedAt || Date.now(),
|
|
39
|
+
lastSeen: Date.now()
|
|
40
|
+
};
|
|
41
|
+
connectedDevices.set(socket.id, device);
|
|
42
|
+
logger_1.default.info(`设备注册: ${device.name} (${device.ip}) [${existingDevice ? '重连' : '新设备'}]`);
|
|
43
|
+
// 通知所有客户端设备列表更新
|
|
44
|
+
io.emit('devices-update', Array.from(connectedDevices.values()));
|
|
45
|
+
});
|
|
46
|
+
// 心跳更新
|
|
47
|
+
socket.on('heartbeat', () => {
|
|
48
|
+
const device = connectedDevices.get(socket.id);
|
|
49
|
+
if (device) {
|
|
50
|
+
device.lastSeen = Date.now();
|
|
51
|
+
connectedDevices.set(socket.id, device);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// 断开连接
|
|
55
|
+
socket.on('disconnect', () => {
|
|
56
|
+
const device = connectedDevices.get(socket.id);
|
|
57
|
+
if (device) {
|
|
58
|
+
logger_1.default.info(`设备断开: ${device.name} (${device.ip})`);
|
|
59
|
+
connectedDevices.delete(socket.id);
|
|
60
|
+
io.emit('devices-update', Array.from(connectedDevices.values()));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
return { io, connectedDevices };
|
|
65
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logDir = void 0;
|
|
7
|
+
const winston_1 = __importDefault(require("winston"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
// #region 日志配置
|
|
12
|
+
const logDir = path_1.default.join(os_1.default.homedir(), '.lanshare', 'logs');
|
|
13
|
+
exports.logDir = logDir;
|
|
14
|
+
// 确保日志目录存在
|
|
15
|
+
if (!fs_1.default.existsSync(logDir)) {
|
|
16
|
+
fs_1.default.mkdirSync(logDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const logFormat = winston_1.default.format.combine(winston_1.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston_1.default.format.errors({ stack: true }), winston_1.default.format.printf(({ timestamp, level, message, stack }) => {
|
|
19
|
+
const msg = `[${timestamp}] ${level.toUpperCase()}: ${message}`;
|
|
20
|
+
return stack ? `${msg}\n${stack}` : msg;
|
|
21
|
+
}));
|
|
22
|
+
// #endregion
|
|
23
|
+
// #region 创建日志实例
|
|
24
|
+
const logger = winston_1.default.createLogger({
|
|
25
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
26
|
+
format: logFormat,
|
|
27
|
+
transports: [
|
|
28
|
+
// 控制台输出(彩色)
|
|
29
|
+
new winston_1.default.transports.Console({
|
|
30
|
+
format: winston_1.default.format.combine(winston_1.default.format.colorize(), logFormat)
|
|
31
|
+
}),
|
|
32
|
+
// 所有日志写入文件
|
|
33
|
+
new winston_1.default.transports.File({
|
|
34
|
+
filename: path_1.default.join(logDir, 'lanshare.log'),
|
|
35
|
+
maxsize: 5 * 1024 * 1024, // 5MB
|
|
36
|
+
maxFiles: 5
|
|
37
|
+
}),
|
|
38
|
+
// 错误日志单独写入文件
|
|
39
|
+
new winston_1.default.transports.File({
|
|
40
|
+
filename: path_1.default.join(logDir, 'error.log'),
|
|
41
|
+
level: 'error',
|
|
42
|
+
maxsize: 5 * 1024 * 1024,
|
|
43
|
+
maxFiles: 5
|
|
44
|
+
})
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
// #endregion
|
|
48
|
+
exports.default = logger;
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lppx/lanshare",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "1.0.1",
|
|
7
|
+
"description": "ts test",
|
|
8
|
+
"bin": {
|
|
9
|
+
"lanshare": "dist/src/cli.js",
|
|
10
|
+
"lsh": "dist/src/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"sync": "ts-node scripts/gitsync.ts",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"start": "node dist/src/cli/index.js",
|
|
16
|
+
"ccx": "npm version patch --force && npm run build && npm publish "
|
|
17
|
+
},
|
|
18
|
+
"author": "lipanpanx",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"public"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=14.0.0"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@types/socket.io": "^3.0.1",
|
|
29
|
+
"commander": "^12.0.0",
|
|
30
|
+
"express": "^4.18.2",
|
|
31
|
+
"multer": "^1.4.5-lts.1",
|
|
32
|
+
"prompts": "^2.4.2",
|
|
33
|
+
"socket.io": "^4.8.3",
|
|
34
|
+
"winston": "^3.19.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/express": "^4.17.21",
|
|
38
|
+
"@types/multer": "^1.4.11",
|
|
39
|
+
"@types/node": "^20.11.0",
|
|
40
|
+
"@types/prompts": "^2.4.9",
|
|
41
|
+
"@types/winston": "^2.4.4",
|
|
42
|
+
"ts-node": "^10.9.2",
|
|
43
|
+
"typescript": "^5.3.3"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
9
|
+
background: #f5f7fa;
|
|
10
|
+
color: #333;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.main-container {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: 24px;
|
|
16
|
+
padding: 24px 32px;
|
|
17
|
+
max-width: 1600px;
|
|
18
|
+
margin: 0 auto;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.left-panel {
|
|
22
|
+
flex: 1;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: 24px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.right-panel {
|
|
29
|
+
width: 320px;
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.section-header {
|
|
34
|
+
display: flex;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
align-items: center;
|
|
37
|
+
margin-bottom: 20px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.section-header h3 {
|
|
41
|
+
font-size: 18px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: #1a1a1a;
|
|
44
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.connected-devices {
|
|
2
|
+
background: white;
|
|
3
|
+
border-radius: 12px;
|
|
4
|
+
padding: 24px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.connected-devices h3 {
|
|
8
|
+
font-size: 18px;
|
|
9
|
+
font-weight: 600;
|
|
10
|
+
color: #1a1a1a;
|
|
11
|
+
margin-bottom: 8px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.devices-subtitle {
|
|
15
|
+
font-size: 13px;
|
|
16
|
+
color: #666;
|
|
17
|
+
margin-bottom: 20px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.devices-list {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: 12px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.device-item {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 12px;
|
|
30
|
+
padding: 12px;
|
|
31
|
+
background: #fafbfc;
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
transition: background 0.2s;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.device-item:hover {
|
|
37
|
+
background: #f5f7fa;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.device-icon {
|
|
41
|
+
width: 40px;
|
|
42
|
+
height: 40px;
|
|
43
|
+
border-radius: 8px;
|
|
44
|
+
background: #e8f4ff;
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
color: #4A90E2;
|
|
49
|
+
font-size: 20px;
|
|
50
|
+
flex-shrink: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.device-info {
|
|
54
|
+
flex: 1;
|
|
55
|
+
min-width: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.device-name {
|
|
59
|
+
font-size: 14px;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
color: #1a1a1a;
|
|
62
|
+
margin-bottom: 2px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.device-ip {
|
|
66
|
+
font-size: 12px;
|
|
67
|
+
color: #666;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.device-status {
|
|
71
|
+
width: 8px;
|
|
72
|
+
height: 8px;
|
|
73
|
+
border-radius: 50%;
|
|
74
|
+
background: #4caf50;
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.drop-zone {
|
|
2
|
+
background: white;
|
|
3
|
+
border: 2px dashed #d0d7de;
|
|
4
|
+
border-radius: 12px;
|
|
5
|
+
padding: 60px 40px;
|
|
6
|
+
text-align: center;
|
|
7
|
+
cursor: pointer;
|
|
8
|
+
transition: all 0.3s ease;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.drop-zone:hover {
|
|
12
|
+
border-color: #4A90E2;
|
|
13
|
+
background: #f8fbff;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.drop-zone.drag-over {
|
|
17
|
+
border-color: #4A90E2;
|
|
18
|
+
background: #e8f4ff;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.drop-zone-icon {
|
|
22
|
+
margin-bottom: 16px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.drop-zone-icon svg {
|
|
26
|
+
display: inline-block;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.drop-zone-title {
|
|
30
|
+
font-size: 18px;
|
|
31
|
+
font-weight: 500;
|
|
32
|
+
color: #1a1a1a;
|
|
33
|
+
margin-bottom: 8px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.drop-zone-subtitle {
|
|
37
|
+
font-size: 14px;
|
|
38
|
+
color: #666;
|
|
39
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
.active-shares {
|
|
2
|
+
background: white;
|
|
3
|
+
border-radius: 12px;
|
|
4
|
+
padding: 24px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.file-count {
|
|
8
|
+
font-size: 14px;
|
|
9
|
+
color: #666;
|
|
10
|
+
background: #f5f7fa;
|
|
11
|
+
padding: 4px 12px;
|
|
12
|
+
border-radius: 12px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.shares-table {
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.table-header {
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: 2fr 1fr 1fr 1fr;
|
|
22
|
+
gap: 16px;
|
|
23
|
+
padding: 12px 16px;
|
|
24
|
+
background: #f5f7fa;
|
|
25
|
+
border-radius: 8px;
|
|
26
|
+
font-size: 11px;
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
color: #666;
|
|
29
|
+
text-transform: uppercase;
|
|
30
|
+
letter-spacing: 0.5px;
|
|
31
|
+
margin-bottom: 8px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.table-body {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
gap: 8px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.file-row {
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: 2fr 1fr 1fr 1fr;
|
|
43
|
+
gap: 16px;
|
|
44
|
+
padding: 16px;
|
|
45
|
+
background: #fafbfc;
|
|
46
|
+
border-radius: 8px;
|
|
47
|
+
align-items: center;
|
|
48
|
+
transition: background 0.2s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.file-row:hover {
|
|
52
|
+
background: #f5f7fa;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.file-info {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 12px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.file-icon {
|
|
62
|
+
width: 40px;
|
|
63
|
+
height: 40px;
|
|
64
|
+
border-radius: 8px;
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
font-size: 20px;
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.file-icon.pdf {
|
|
73
|
+
background: #ffe5e5;
|
|
74
|
+
color: #d32f2f;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.file-icon.zip {
|
|
78
|
+
background: #e3f2fd;
|
|
79
|
+
color: #1976d2;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.file-icon.video {
|
|
83
|
+
background: #f3e5f5;
|
|
84
|
+
color: #7b1fa2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.file-details {
|
|
88
|
+
min-width: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.file-name {
|
|
92
|
+
font-size: 14px;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
color: #1a1a1a;
|
|
95
|
+
margin-bottom: 4px;
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
text-overflow: ellipsis;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.file-meta {
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
color: #666;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.file-size {
|
|
107
|
+
font-size: 14px;
|
|
108
|
+
color: #666;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.file-downloads {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: 8px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.download-progress {
|
|
118
|
+
flex: 1;
|
|
119
|
+
height: 6px;
|
|
120
|
+
background: #e0e0e0;
|
|
121
|
+
border-radius: 3px;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
max-width: 100px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.progress-bar {
|
|
127
|
+
height: 100%;
|
|
128
|
+
background: #4A90E2;
|
|
129
|
+
border-radius: 3px;
|
|
130
|
+
transition: width 0.3s;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.download-count {
|
|
134
|
+
font-size: 14px;
|
|
135
|
+
color: #666;
|
|
136
|
+
min-width: 20px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.file-actions {
|
|
140
|
+
display: flex;
|
|
141
|
+
gap: 8px;
|
|
142
|
+
justify-content: flex-end;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.action-btn {
|
|
146
|
+
width: 32px;
|
|
147
|
+
height: 32px;
|
|
148
|
+
border: none;
|
|
149
|
+
background: transparent;
|
|
150
|
+
border-radius: 6px;
|
|
151
|
+
cursor: pointer;
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
justify-content: center;
|
|
155
|
+
color: #666;
|
|
156
|
+
transition: all 0.2s;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.action-btn:hover {
|
|
160
|
+
background: #e8f4ff;
|
|
161
|
+
color: #4A90E2;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.action-btn.delete:hover {
|
|
165
|
+
background: #ffebee;
|
|
166
|
+
color: #d32f2f;
|
|
167
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.header {
|
|
2
|
+
background: white;
|
|
3
|
+
padding: 16px 32px;
|
|
4
|
+
display: flex;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
align-items: center;
|
|
7
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.header-left {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 12px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.share-icon {
|
|
17
|
+
color: #4A90E2;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.title {
|
|
21
|
+
font-size: 20px;
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
color: #1a1a1a;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.header-right {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 16px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ip-address {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 6px;
|
|
36
|
+
padding: 6px 12px;
|
|
37
|
+
background: #f5f7fa;
|
|
38
|
+
border-radius: 6px;
|
|
39
|
+
font-size: 14px;
|
|
40
|
+
color: #666;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.user-avatar {
|
|
44
|
+
width: 36px;
|
|
45
|
+
height: 36px;
|
|
46
|
+
border-radius: 50%;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>LAN Share Control</title>
|
|
7
|
+
<link rel="stylesheet" href="/css/base.css">
|
|
8
|
+
<link rel="stylesheet" href="/css/header.css">
|
|
9
|
+
<link rel="stylesheet" href="/css/drop-zone.css">
|
|
10
|
+
<link rel="stylesheet" href="/css/file-list.css">
|
|
11
|
+
<link rel="stylesheet" href="/css/device-list.css">
|
|
12
|
+
<link rel="stylesheet" href="/css/responsive.css">
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<header class="header">
|
|
16
|
+
<div class="header-left">
|
|
17
|
+
<svg class="share-icon" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
18
|
+
<path d="M18 8C19.6569 8 21 6.65685 21 5C21 3.34315 19.6569 2 18 2C16.3431 2 15 3.34315 15 5C15 6.65685 16.3431 8 18 8Z" stroke="currentColor" stroke-width="2"/>
|
|
19
|
+
<path d="M6 15C7.65685 15 9 13.6569 9 12C9 10.3431 7.65685 9 6 9C4.34315 9 3 10.3431 3 12C3 13.6569 4.34315 15 6 15Z" stroke="currentColor" stroke-width="2"/>
|
|
20
|
+
<path d="M18 22C19.6569 22 21 20.6569 21 19C21 17.3431 19.6569 16 18 16C16.3431 16 15 17.3431 15 19C15 20.6569 16.3431 22 18 22Z" stroke="currentColor" stroke-width="2"/>
|
|
21
|
+
<path d="M8.59 13.51L15.42 17.49M15.41 6.51L8.59 10.49" stroke="currentColor" stroke-width="2"/>
|
|
22
|
+
</svg>
|
|
23
|
+
<h1 class="title">LAN Share Control</h1>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="header-right">
|
|
26
|
+
<span class="ip-address" id="ipAddress">
|
|
27
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
28
|
+
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2"/>
|
|
29
|
+
<line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" stroke-width="2"/>
|
|
30
|
+
<line x1="9" y1="9" x2="9" y2="21" stroke="currentColor" stroke-width="2"/>
|
|
31
|
+
</svg>
|
|
32
|
+
<span id="ipText">192.168.1.15</span>
|
|
33
|
+
</span>
|
|
34
|
+
<div class="user-avatar">
|
|
35
|
+
<svg width="32" height="32" viewBox="0 0 32 32">
|
|
36
|
+
<circle cx="16" cy="16" r="16" fill="#FF9800"/>
|
|
37
|
+
<circle cx="16" cy="12" r="5" fill="white"/>
|
|
38
|
+
<path d="M8 26C8 21 11 18 16 18C21 18 24 21 24 26" fill="white"/>
|
|
39
|
+
</svg>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</header>
|
|
43
|
+
|
|
44
|
+
<div class="main-container">
|
|
45
|
+
<div class="left-panel">
|
|
46
|
+
<div class="drop-zone" id="dropZone">
|
|
47
|
+
<div class="drop-zone-icon">
|
|
48
|
+
<svg width="64" height="64" viewBox="0 0 24 24" fill="none">
|
|
49
|
+
<path d="M7 18C4.79086 18 3 16.2091 3 14C3 11.7909 4.79086 10 7 10C7.17822 10 7.35381 10.0104 7.52614 10.0305C7.84294 7.14916 10.2207 5 13 5C15.7793 5 18.1571 7.14916 18.4739 10.0305C18.6462 10.0104 18.8218 10 19 10C21.2091 10 23 11.7909 23 14C23 16.2091 21.2091 18 19 18H7Z" stroke="#4A90E2" stroke-width="2"/>
|
|
50
|
+
<path d="M12 12V19M12 12L9 15M12 12L15 15" stroke="#4A90E2" stroke-width="2" stroke-linecap="round"/>
|
|
51
|
+
</svg>
|
|
52
|
+
</div>
|
|
53
|
+
<h2 class="drop-zone-title">Drag & Drop files here</h2>
|
|
54
|
+
<p class="drop-zone-subtitle">or click to browse your computer</p>
|
|
55
|
+
<input type="file" id="fileInput" multiple hidden>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="active-shares">
|
|
59
|
+
<div class="section-header">
|
|
60
|
+
<h3>Active Shares</h3>
|
|
61
|
+
<span class="file-count" id="fileCount">0 Files</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="shares-table">
|
|
64
|
+
<div class="table-header">
|
|
65
|
+
<div class="col-name">FILE NAME</div>
|
|
66
|
+
<div class="col-size">SIZE</div>
|
|
67
|
+
<div class="col-downloads">DOWNLOADS</div>
|
|
68
|
+
<div class="col-actions">ACTIONS</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="table-body" id="fileList"></div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="right-panel">
|
|
76
|
+
<div class="connected-devices">
|
|
77
|
+
<h3>Connected Devices</h3>
|
|
78
|
+
<p class="devices-subtitle">Devices currently accessing shared files</p>
|
|
79
|
+
<div class="devices-list" id="devicesList"></div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<script src="/socket.io/socket.io.js"></script>
|
|
85
|
+
<script src="/js/socket.js"></script>
|
|
86
|
+
<script src="/js/upload.js"></script>
|
|
87
|
+
<script src="/js/file-list.js"></script>
|
|
88
|
+
<script src="/js/device-list.js"></script>
|
|
89
|
+
<script src="/js/api.js"></script>
|
|
90
|
+
<script src="/js/main.js"></script>
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|