lanet 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -21
- data/Gemfile.lock +1 -1
- data/README.md +86 -2
- data/index.html +424 -223
- data/lib/lanet/cli.rb +110 -62
- data/lib/lanet/encryptor.rb +94 -16
- data/lib/lanet/scanner.rb +101 -135
- data/lib/lanet/signer.rb +47 -0
- data/lib/lanet/version.rb +1 -1
- metadata +3 -2
data/index.html
CHANGED
@@ -1,233 +1,434 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html lang="en">
|
3
3
|
<head>
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Lanet - Secure Network Communications Library</title>
|
7
|
+
<style>
|
8
|
+
body {
|
9
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
10
|
+
line-height: 1.6;
|
11
|
+
color: #333;
|
12
|
+
max-width: 900px;
|
13
|
+
margin: 0 auto;
|
14
|
+
padding: 20px;
|
15
|
+
background-color: #f9f9f9;
|
16
|
+
}
|
17
|
+
h1 {
|
18
|
+
color: #2c3e50;
|
19
|
+
border-bottom: 2px solid #3498db;
|
20
|
+
padding-bottom: 10px;
|
21
|
+
}
|
22
|
+
h2 {
|
23
|
+
color: #2980b9;
|
24
|
+
margin-top: 30px;
|
25
|
+
}
|
26
|
+
h3 {
|
27
|
+
color: #16a085;
|
28
|
+
margin-top: 25px;
|
29
|
+
}
|
30
|
+
h4 {
|
31
|
+
color: #27ae60;
|
32
|
+
margin-top: 20px;
|
33
|
+
}
|
34
|
+
pre {
|
35
|
+
background-color: #f8f8f8;
|
36
|
+
border: 1px solid #ddd;
|
37
|
+
border-left: 3px solid #3498db;
|
38
|
+
padding: 15px;
|
39
|
+
overflow-x: auto;
|
40
|
+
border-radius: 4px;
|
41
|
+
}
|
42
|
+
code {
|
43
|
+
font-family: 'Courier New', Courier, monospace;
|
44
|
+
}
|
45
|
+
.container {
|
46
|
+
background-color: #fff;
|
47
|
+
border-radius: 8px;
|
48
|
+
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
49
|
+
padding: 30px;
|
50
|
+
}
|
51
|
+
.feature {
|
52
|
+
margin: 20px 0;
|
53
|
+
padding-left: 20px;
|
54
|
+
border-left: 4px solid #2ecc71;
|
55
|
+
}
|
56
|
+
.security-feature {
|
57
|
+
background-color: #e8f4fc;
|
58
|
+
padding: 15px;
|
59
|
+
margin: 10px 0;
|
60
|
+
border-radius: 6px;
|
61
|
+
}
|
62
|
+
.cli-example {
|
63
|
+
background-color: #2c3e50;
|
64
|
+
color: #ecf0f1;
|
65
|
+
padding: 12px 18px;
|
66
|
+
margin: 10px 0;
|
67
|
+
border-radius: 6px;
|
68
|
+
font-family: 'Courier New', Courier, monospace;
|
69
|
+
position: relative;
|
70
|
+
}
|
71
|
+
.cli-example::before {
|
72
|
+
content: "$";
|
73
|
+
color: #3498db;
|
74
|
+
margin-right: 10px;
|
75
|
+
font-weight: bold;
|
76
|
+
}
|
77
|
+
.cli-section {
|
78
|
+
margin-bottom: 30px;
|
79
|
+
background-color: #f8f9fa;
|
80
|
+
padding: 20px;
|
81
|
+
border-radius: 6px;
|
82
|
+
}
|
83
|
+
.output-example {
|
84
|
+
background-color: #f0f0f0;
|
85
|
+
padding: 12px;
|
86
|
+
margin: 10px 0;
|
87
|
+
border-radius: 6px;
|
88
|
+
font-family: 'Courier New', Courier, monospace;
|
89
|
+
font-size: 0.9em;
|
90
|
+
border-left: 3px solid #9b59b6;
|
91
|
+
}
|
92
|
+
.tab-container {
|
93
|
+
border: 1px solid #ddd;
|
94
|
+
border-radius: 6px;
|
95
|
+
overflow: hidden;
|
96
|
+
margin: 20px 0;
|
97
|
+
}
|
98
|
+
.tab-buttons {
|
99
|
+
display: flex;
|
100
|
+
background-color: #f5f5f5;
|
101
|
+
}
|
102
|
+
.tab-button {
|
103
|
+
padding: 10px 20px;
|
104
|
+
background-color: transparent;
|
105
|
+
border: none;
|
106
|
+
cursor: pointer;
|
107
|
+
border-right: 1px solid #ddd;
|
108
|
+
transition: background-color 0.3s;
|
109
|
+
}
|
110
|
+
.tab-button:hover, .tab-button.active {
|
111
|
+
background-color: #e0e0e0;
|
112
|
+
}
|
113
|
+
.tab-content {
|
114
|
+
display: none;
|
115
|
+
padding: 15px;
|
116
|
+
}
|
117
|
+
.tab-content.active {
|
118
|
+
display: block;
|
119
|
+
}
|
120
|
+
.badge {
|
121
|
+
display: inline-block;
|
122
|
+
padding: 3px 8px;
|
123
|
+
border-radius: 4px;
|
124
|
+
font-size: 12px;
|
125
|
+
font-weight: bold;
|
126
|
+
}
|
127
|
+
.badge-new {
|
128
|
+
background-color: #2ecc71;
|
129
|
+
color: white;
|
130
|
+
}
|
131
|
+
.note {
|
132
|
+
background-color: #fff8e1;
|
133
|
+
padding: 12px;
|
134
|
+
border-left: 4px solid #ffc107;
|
135
|
+
margin: 10px 0;
|
136
|
+
border-radius: 4px;
|
137
|
+
}
|
138
|
+
</style>
|
102
139
|
</head>
|
103
140
|
<body>
|
104
|
-
<header>
|
105
141
|
<div class="container">
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
<
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
<
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
142
|
+
<h1>Lanet</h1>
|
143
|
+
<p>A secure network communication library that enables reliable and protected message exchange between devices on the same network.</p>
|
144
|
+
|
145
|
+
<h2>Key Features</h2>
|
146
|
+
|
147
|
+
<div class="feature">
|
148
|
+
<h3>Encrypted Communication</h3>
|
149
|
+
<p>Protect your messages with AES-256-CBC encryption to ensure confidentiality during transmission.</p>
|
150
|
+
</div>
|
151
|
+
|
152
|
+
<div class="feature">
|
153
|
+
<h3>Digital Signatures <span class="badge badge-new">New in v0.2.0</span></h3>
|
154
|
+
<p>Verify message authenticity and integrity with RSA-based digital signatures.</p>
|
155
|
+
|
156
|
+
<div class="security-feature">
|
157
|
+
<h4>Benefits of Digital Signatures:</h4>
|
158
|
+
<ul>
|
159
|
+
<li><strong>Authentication</strong>: Verify that the message came from the claimed sender</li>
|
160
|
+
<li><strong>Data Integrity</strong>: Ensure the message hasn't been tampered with during transit</li>
|
161
|
+
<li><strong>Non-repudiation</strong>: Senders cannot deny sending a message they signed</li>
|
162
|
+
<li><strong>Protection against MITM attacks</strong>: Detect man-in-the-middle tampering attempts</li>
|
163
|
+
</ul>
|
164
|
+
</div>
|
165
|
+
</div>
|
166
|
+
|
167
|
+
<div class="feature">
|
168
|
+
<h3>Network Discovery</h3>
|
169
|
+
<p>Automatically find active devices on your local network with advanced scanning capabilities.</p>
|
170
|
+
</div>
|
171
|
+
|
172
|
+
<div class="feature">
|
173
|
+
<h3>Broadcasting</h3>
|
174
|
+
<p>Send messages to all devices on your network simultaneously.</p>
|
175
|
+
</div>
|
176
|
+
|
177
|
+
<h2>Command Line Interface</h2>
|
178
|
+
|
179
|
+
<div class="note">
|
180
|
+
<strong>Note:</strong> All Lanet commands have detailed help available. Try <code>lanet [command] --help</code> for more options.
|
181
|
+
</div>
|
182
|
+
|
183
|
+
<div class="cli-section">
|
184
|
+
<h3>Digital Signature Commands <span class="badge badge-new">New in v0.2.0</span></h3>
|
185
|
+
|
186
|
+
<h4>Generate a Key Pair</h4>
|
187
|
+
<p>Create RSA keys for signing and verifying messages:</p>
|
188
|
+
<div class="cli-example">
|
189
|
+
lanet keygen
|
190
|
+
</div>
|
191
|
+
|
192
|
+
<p>Generate keys with custom options:</p>
|
193
|
+
<div class="cli-example">
|
194
|
+
lanet keygen --bits 4096 --output ~/.lanet_keys
|
195
|
+
</div>
|
196
|
+
|
197
|
+
<div class="output-example">
|
198
|
+
Key pair generated!
|
199
|
+
Private key saved to: /home/user/.lanet_keys/lanet_private.key
|
200
|
+
Public key saved to: /home/user/.lanet_keys/lanet_public.key
|
201
|
+
|
202
|
+
IMPORTANT: Keep your private key secure and never share it.
|
203
|
+
Share your public key with others who need to verify your messages.
|
204
|
+
</div>
|
205
|
+
|
206
|
+
<h4>Send a Signed Message</h4>
|
207
|
+
<div class="cli-example">
|
208
|
+
lanet send --target 192.168.1.5 --message "Signed message" --private-key-file lanet_private.key
|
209
|
+
</div>
|
210
|
+
|
211
|
+
<h4>Send a Signed & Encrypted Message</h4>
|
212
|
+
<div class="cli-example">
|
213
|
+
lanet send --target 192.168.1.5 --message "Secure signed message" --key "my_secret_key" --private-key-file lanet_private.key
|
214
|
+
</div>
|
215
|
+
|
216
|
+
<h4>Broadcast a Signed Message</h4>
|
217
|
+
<div class="cli-example">
|
218
|
+
lanet broadcast --message "Important announcement" --private-key-file lanet_private.key
|
219
|
+
</div>
|
220
|
+
|
221
|
+
<h4>Listen for and Verify Signed Messages</h4>
|
222
|
+
<div class="cli-example">
|
223
|
+
lanet listen --public-key-file lanet_public.key
|
224
|
+
</div>
|
225
|
+
|
226
|
+
<h4>Listen for Encrypted & Signed Messages</h4>
|
227
|
+
<div class="cli-example">
|
228
|
+
lanet listen --encryption-key "my_secret_key" --public-key-file lanet_public.key
|
229
|
+
</div>
|
230
|
+
|
231
|
+
<p>Example output when receiving a signed message:</p>
|
232
|
+
<div class="output-example">
|
233
|
+
Message from 192.168.1.5:
|
234
|
+
Content: Hello, this is a signed message
|
235
|
+
Signature: ✓ VERIFIED
|
236
|
+
----------------------------------------
|
237
|
+
</div>
|
238
|
+
</div>
|
239
|
+
|
240
|
+
<div class="cli-section">
|
241
|
+
<h3>Basic Message Commands</h3>
|
242
|
+
|
243
|
+
<h4>Send a Message</h4>
|
244
|
+
<div class="cli-example">
|
245
|
+
lanet send --target 192.168.1.5 --message "Hello there!"
|
246
|
+
</div>
|
247
|
+
|
248
|
+
<h4>Send an Encrypted Message</h4>
|
249
|
+
<div class="cli-example">
|
250
|
+
lanet send --target 192.168.1.5 --message "Secret message" --key "my_secret_key"
|
251
|
+
</div>
|
252
|
+
|
253
|
+
<h4>Broadcast a Message</h4>
|
254
|
+
<div class="cli-example">
|
255
|
+
lanet broadcast --message "Announcement for everyone!"
|
256
|
+
</div>
|
257
|
+
|
258
|
+
<h4>Listen for Messages</h4>
|
259
|
+
<div class="cli-example">
|
260
|
+
lanet listen
|
261
|
+
</div>
|
262
|
+
|
263
|
+
<h4>Listen for Encrypted Messages</h4>
|
264
|
+
<div class="cli-example">
|
265
|
+
lanet listen --encryption-key "my_secret_key"
|
266
|
+
</div>
|
267
|
+
</div>
|
268
|
+
|
269
|
+
<div class="cli-section">
|
270
|
+
<h3>Network Discovery Commands</h3>
|
271
|
+
|
272
|
+
<h4>Scan Network</h4>
|
273
|
+
<div class="cli-example">
|
274
|
+
lanet scan --range 192.168.1.0/24
|
275
|
+
</div>
|
276
|
+
|
277
|
+
<h4>Detailed Network Scan</h4>
|
278
|
+
<div class="cli-example">
|
279
|
+
lanet scan --range 192.168.1.0/24 --verbose --threads 16 --timeout 2
|
280
|
+
</div>
|
281
|
+
|
282
|
+
<h4>Ping a Host</h4>
|
283
|
+
<div class="cli-example">
|
284
|
+
lanet ping 192.168.1.5
|
285
|
+
</div>
|
286
|
+
|
287
|
+
<h4>Continuous Ping</h4>
|
288
|
+
<div class="cli-example">
|
289
|
+
lanet ping 192.168.1.5 --continuous
|
290
|
+
</div>
|
291
|
+
|
292
|
+
<h4>Ping Multiple Hosts</h4>
|
293
|
+
<div class="cli-example">
|
294
|
+
lanet ping --hosts 192.168.1.5,192.168.1.6,192.168.1.7 --count 5
|
295
|
+
</div>
|
296
|
+
</div>
|
297
|
+
|
298
|
+
<h2>Ruby Code Examples</h2>
|
299
|
+
|
300
|
+
<div class="tab-container">
|
301
|
+
<div class="tab-buttons">
|
302
|
+
<button class="tab-button active" onclick="openTab(event, 'tab-basic')">Basic Usage</button>
|
303
|
+
<button class="tab-button" onclick="openTab(event, 'tab-signatures')">Digital Signatures</button>
|
304
|
+
<button class="tab-button" onclick="openTab(event, 'tab-advanced')">Advanced Usage</button>
|
305
|
+
</div>
|
306
|
+
|
307
|
+
<div id="tab-basic" class="tab-content active">
|
308
|
+
<pre><code>require 'lanet'
|
309
|
+
|
310
|
+
# Send a message to a specific IP
|
311
|
+
sender = Lanet::Sender.new(5000)
|
312
|
+
sender.send_to('192.168.1.5', 'Hello from Ruby!')
|
313
|
+
|
314
|
+
# Listen for incoming messages
|
315
|
+
receiver = Lanet::Receiver.new(5000)
|
189
316
|
receiver.listen do |data, ip|
|
190
317
|
puts "Received from #{ip}: #{data}"
|
318
|
+
end
|
319
|
+
|
320
|
+
# Work with encrypted messages
|
321
|
+
encrypted = Lanet::Encryptor.prepare_message('Secret message', 'my_encryption_key')
|
322
|
+
# Send the encrypted message
|
323
|
+
sender.send_to('192.168.1.5', encrypted)</code></pre>
|
324
|
+
</div>
|
325
|
+
|
326
|
+
<div id="tab-signatures" class="tab-content">
|
327
|
+
<pre><code>require 'lanet'
|
328
|
+
|
329
|
+
# Generate RSA key pair for signing
|
330
|
+
key_pair = Lanet::Signer.generate_key_pair
|
331
|
+
private_key = key_pair[:private_key]
|
332
|
+
public_key = key_pair[:public_key]
|
333
|
+
|
334
|
+
# Set up sender and receiver
|
335
|
+
sender = Lanet::Sender.new(9000)
|
336
|
+
encryption_key = "secret-key-123"
|
337
|
+
|
338
|
+
# Send a signed and encrypted message
|
339
|
+
message = "Hello, secure world!"
|
340
|
+
prepared_message = Lanet::Encryptor.prepare_message(
|
341
|
+
message,
|
342
|
+
encryption_key,
|
343
|
+
private_key
|
344
|
+
)
|
345
|
+
sender.send_to("192.168.1.5", prepared_message)
|
346
|
+
|
347
|
+
# Receive and verify messages
|
348
|
+
receiver = Lanet::Receiver.new(9000)
|
349
|
+
receiver.listen do |data, sender_ip|
|
350
|
+
result = Lanet::Encryptor.process_message(
|
351
|
+
data,
|
352
|
+
encryption_key,
|
353
|
+
public_key
|
354
|
+
)
|
355
|
+
|
356
|
+
puts "Message: #{result[:content]}"
|
357
|
+
puts "Verified: #{result[:verified]}"
|
358
|
+
puts "Verification status: #{result[:verification_status]}"
|
191
359
|
end</code></pre>
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
360
|
+
</div>
|
361
|
+
|
362
|
+
<div id="tab-advanced" class="tab-content">
|
363
|
+
<pre><code>require 'lanet'
|
364
|
+
|
365
|
+
# Create a scanner and find active devices
|
366
|
+
scanner = Lanet::Scanner.new
|
367
|
+
active_hosts = scanner.scan('192.168.1.0/24', 1, 32, true)
|
368
|
+
|
369
|
+
active_hosts.each do |host|
|
370
|
+
puts "Host: #{host[:ip]}, Hostname: #{host[:hostname]}"
|
371
|
+
puts "Response time: #{host[:response_time]}ms"
|
372
|
+
|
373
|
+
if host[:ports]
|
374
|
+
puts "Open ports:"
|
375
|
+
host[:ports].each do |port, service|
|
376
|
+
puts " - #{port}: #{service}"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Ping a specific host
|
382
|
+
pinger = Lanet::Ping.new
|
383
|
+
result = pinger.ping_host('192.168.1.5', true)
|
384
|
+
puts "Host reachable: #{result[:status]}"
|
385
|
+
puts "Response time: #{result[:response_time]}ms"
|
386
|
+
|
387
|
+
# Broadcast a signed and encrypted message
|
388
|
+
encryption_key = "network-shared-key"
|
389
|
+
message = "Important system notification"
|
390
|
+
signed_message = Lanet::Encryptor.prepare_message(
|
391
|
+
message,
|
392
|
+
encryption_key,
|
393
|
+
private_key
|
394
|
+
)
|
395
|
+
sender.broadcast(signed_message)</code></pre>
|
396
|
+
</div>
|
397
|
+
</div>
|
398
|
+
|
399
|
+
<h2>Installation</h2>
|
400
|
+
|
401
|
+
<p>Add this to your Gemfile:</p>
|
402
|
+
<pre><code>gem 'lanet'</code></pre>
|
403
|
+
|
404
|
+
<p>Or install directly:</p>
|
405
|
+
<pre><code>gem install lanet</code></pre>
|
406
|
+
|
407
|
+
<h2>Documentation</h2>
|
408
|
+
<p>For complete documentation, please visit the <a href="https://github.com/davidesantangelo/lanet">GitHub repository</a>.</p>
|
409
|
+
|
410
|
+
<footer style="margin-top: 40px; text-align: center; color: #7f8c8d;">
|
411
|
+
<p>Lanet v0.2.0 - Secure Network Communications Library</p>
|
412
|
+
</footer>
|
413
|
+
</div>
|
414
|
+
|
415
|
+
<script>
|
416
|
+
function openTab(evt, tabName) {
|
417
|
+
var i, tabContent, tabButtons;
|
418
|
+
|
419
|
+
tabContent = document.getElementsByClassName("tab-content");
|
420
|
+
for (i = 0; i < tabContent.length; i++) {
|
421
|
+
tabContent[i].className = tabContent[i].className.replace(" active", "");
|
422
|
+
}
|
423
|
+
|
424
|
+
tabButtons = document.getElementsByClassName("tab-button");
|
425
|
+
for (i = 0; i < tabButtons.length; i++) {
|
426
|
+
tabButtons[i].className = tabButtons[i].className.replace(" active", "");
|
427
|
+
}
|
428
|
+
|
429
|
+
document.getElementById(tabName).className += " active";
|
430
|
+
evt.currentTarget.className += " active";
|
431
|
+
}
|
432
|
+
</script>
|
232
433
|
</body>
|
233
|
-
</html>
|
434
|
+
</html>
|