rbzk 0.1.6 → 0.1.8
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/README.md +1 -1
- data/bin/console +3 -3
- data/lib/rbzk/attendance.rb +19 -0
- data/lib/rbzk/cli/commands.rb +228 -214
- data/lib/rbzk/cli/config.rb +2 -2
- data/lib/rbzk/constants.rb +3 -3
- data/lib/rbzk/exceptions.rb +9 -3
- data/lib/rbzk/finger.rb +1 -1
- data/lib/rbzk/user.rb +11 -11
- data/lib/rbzk/version.rb +1 -1
- data/lib/rbzk/zk.rb +498 -651
- data/lib/rbzk.rb +7 -7
- metadata +8 -8
data/lib/rbzk/cli/commands.rb
CHANGED
|
@@ -13,7 +13,6 @@ require 'terminal-table'
|
|
|
13
13
|
module RBZK
|
|
14
14
|
module CLI
|
|
15
15
|
class Commands < Thor
|
|
16
|
-
|
|
17
16
|
# Global options
|
|
18
17
|
class_option :ip, type: :string, desc: 'IP address of the device'
|
|
19
18
|
class_option :port, type: :numeric, desc: 'Port number (default: 4370)'
|
|
@@ -24,10 +23,13 @@ module RBZK
|
|
|
24
23
|
class_option :no_ping, type: :boolean, desc: 'Skip ping check'
|
|
25
24
|
class_option :encoding, type: :string, desc: 'Encoding for strings (default: UTF-8)'
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
def self.exit_on_failure?
|
|
27
|
+
true
|
|
28
|
+
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
desc 'info [IP]', 'Get device information'
|
|
30
31
|
|
|
32
|
+
def info(ip = nil)
|
|
31
33
|
# Use IP from options if not provided as argument
|
|
32
34
|
ip ||= options[:ip] || @config['ip']
|
|
33
35
|
|
|
@@ -59,16 +61,16 @@ module RBZK
|
|
|
59
61
|
if defined?(::Terminal) && defined?(::Terminal::Table)
|
|
60
62
|
# Pretty table output
|
|
61
63
|
table = ::Terminal::Table.new do |t|
|
|
62
|
-
t.title =
|
|
64
|
+
t.title = 'Device Information'
|
|
63
65
|
device_info.each do |key, value|
|
|
64
|
-
t << [
|
|
66
|
+
t << [key, value]
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
puts table
|
|
69
71
|
else
|
|
70
72
|
# Fallback plain text output
|
|
71
|
-
puts
|
|
73
|
+
puts 'Device Information:'
|
|
72
74
|
device_info.each do |key, value|
|
|
73
75
|
puts "#{key}: #{value}"
|
|
74
76
|
end
|
|
@@ -76,61 +78,61 @@ module RBZK
|
|
|
76
78
|
end
|
|
77
79
|
end
|
|
78
80
|
|
|
79
|
-
desc
|
|
81
|
+
desc 'refresh [IP]', 'Refresh device data'
|
|
80
82
|
|
|
81
83
|
def refresh(ip = nil)
|
|
82
84
|
# Use IP from options if not provided as argument
|
|
83
85
|
ip ||= options[:ip] || @config['ip']
|
|
84
86
|
|
|
85
87
|
with_connection(ip, options) do |conn|
|
|
86
|
-
puts
|
|
88
|
+
puts 'Refreshing device data...'
|
|
87
89
|
result = conn.refresh_data
|
|
88
|
-
puts
|
|
90
|
+
puts '✓ Device data refreshed successfully!' if result
|
|
89
91
|
end
|
|
90
92
|
end
|
|
91
93
|
|
|
92
|
-
desc
|
|
94
|
+
desc 'users [IP]', 'Get users from the device'
|
|
93
95
|
|
|
94
96
|
def users(ip = nil)
|
|
95
97
|
# Use IP from options if not provided as argument
|
|
96
98
|
ip ||= options[:ip] || @config['ip']
|
|
97
99
|
with_connection(ip, options) do |conn|
|
|
98
|
-
puts
|
|
100
|
+
puts 'Getting users...'
|
|
99
101
|
users = conn.get_users
|
|
100
102
|
display_users(users)
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
# Add aliases for common log commands
|
|
105
|
-
desc
|
|
106
|
-
map
|
|
107
|
+
desc 'logs-today [IP]', "Get today's attendance logs"
|
|
108
|
+
map 'logs-today' => 'logs'
|
|
107
109
|
|
|
108
110
|
def logs_today(ip = nil)
|
|
109
|
-
invoke :logs, [
|
|
111
|
+
invoke :logs, [ip], { today: true }.merge(options)
|
|
110
112
|
end
|
|
111
113
|
|
|
112
|
-
desc
|
|
113
|
-
map
|
|
114
|
+
desc 'logs-yesterday [IP]', "Get yesterday's attendance logs"
|
|
115
|
+
map 'logs-yesterday' => 'logs'
|
|
114
116
|
|
|
115
117
|
def logs_yesterday(ip = nil)
|
|
116
|
-
invoke :logs, [
|
|
118
|
+
invoke :logs, [ip], { yesterday: true }.merge(options)
|
|
117
119
|
end
|
|
118
120
|
|
|
119
|
-
desc
|
|
120
|
-
map
|
|
121
|
+
desc 'logs-week [IP]', "Get this week's attendance logs"
|
|
122
|
+
map 'logs-week' => 'logs'
|
|
121
123
|
|
|
122
124
|
def logs_week(ip = nil)
|
|
123
|
-
invoke :logs, [
|
|
125
|
+
invoke :logs, [ip], { week: true }.merge(options)
|
|
124
126
|
end
|
|
125
127
|
|
|
126
|
-
desc
|
|
127
|
-
map
|
|
128
|
+
desc 'logs-month [IP]', "Get this month's attendance logs"
|
|
129
|
+
map 'logs-month' => 'logs'
|
|
128
130
|
|
|
129
131
|
def logs_month(ip = nil)
|
|
130
|
-
invoke :logs, [
|
|
132
|
+
invoke :logs, [ip], { month: true }.merge(options)
|
|
131
133
|
end
|
|
132
134
|
|
|
133
|
-
desc
|
|
135
|
+
desc 'logs-all [IP]', 'Get all attendance logs without limit'
|
|
134
136
|
|
|
135
137
|
def logs_all(ip = nil)
|
|
136
138
|
# Use IP from options if not provided as argument
|
|
@@ -138,7 +140,7 @@ module RBZK
|
|
|
138
140
|
|
|
139
141
|
with_connection(ip, options) do |conn|
|
|
140
142
|
# Get attendance logs
|
|
141
|
-
puts
|
|
143
|
+
puts 'Getting all attendance logs (this may take a while)...'
|
|
142
144
|
logs = conn.get_attendance_logs
|
|
143
145
|
total_logs = logs.size
|
|
144
146
|
puts "Total logs: #{total_logs}" if options[:verbose]
|
|
@@ -150,15 +152,17 @@ module RBZK
|
|
|
150
152
|
if defined?(::Terminal) && defined?(::Terminal::Table)
|
|
151
153
|
# Pretty table output
|
|
152
154
|
table = ::Terminal::Table.new do |t|
|
|
153
|
-
t.title =
|
|
154
|
-
t.headings = [ 'User ID', 'Time', 'Status' ]
|
|
155
|
+
t.title = 'All Attendance Logs (Showing All Records)'
|
|
156
|
+
t.headings = ['UID', 'User ID', 'Time', 'Status', 'Punch Type']
|
|
155
157
|
|
|
156
158
|
# Show all logs in the table
|
|
157
159
|
logs.each do |log|
|
|
158
160
|
t << [
|
|
161
|
+
log.uid,
|
|
159
162
|
log.user_id,
|
|
160
163
|
log.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
|
161
|
-
|
|
164
|
+
log.status,
|
|
165
|
+
log.punch_name
|
|
162
166
|
]
|
|
163
167
|
end
|
|
164
168
|
end
|
|
@@ -167,7 +171,7 @@ module RBZK
|
|
|
167
171
|
else
|
|
168
172
|
# Fallback plain text output
|
|
169
173
|
logs.each do |log|
|
|
170
|
-
puts " User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}"
|
|
174
|
+
puts " UID: #{log.uid}, User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}, Punch: #{log.punch_name}"
|
|
171
175
|
end
|
|
172
176
|
end
|
|
173
177
|
else
|
|
@@ -176,31 +180,32 @@ module RBZK
|
|
|
176
180
|
end
|
|
177
181
|
end
|
|
178
182
|
|
|
179
|
-
desc
|
|
183
|
+
desc 'logs-custom START_DATE END_DATE [IP]', 'Get logs for a custom date range (YYYY-MM-DD)'
|
|
180
184
|
|
|
181
185
|
def logs_custom(start_date, end_date, ip = nil)
|
|
182
186
|
# Use IP from options if not provided as argument
|
|
183
187
|
ip ||= options[:ip] || @config['ip']
|
|
184
|
-
invoke :logs, [
|
|
188
|
+
invoke :logs, [ip], { start_date: start_date, end_date: end_date }.merge(options)
|
|
185
189
|
end
|
|
186
190
|
|
|
187
|
-
desc
|
|
191
|
+
desc 'logs [IP]', 'Get attendance logs'
|
|
188
192
|
method_option :today, type: :boolean, desc: "Get only today's logs"
|
|
189
193
|
method_option :yesterday, type: :boolean, desc: "Get only yesterday's logs"
|
|
190
194
|
method_option :week, type: :boolean, desc: "Get this week's logs"
|
|
191
195
|
method_option :month, type: :boolean, desc: "Get this month's logs"
|
|
192
|
-
method_option :start_date, type: :string, desc:
|
|
193
|
-
method_option :end_date, type: :string, desc:
|
|
194
|
-
method_option :start_time, type: :string, desc:
|
|
195
|
-
method_option :end_time, type: :string, desc:
|
|
196
|
-
method_option :limit, type: :numeric, default: 25,
|
|
196
|
+
method_option :start_date, type: :string, desc: 'Start date for custom range (YYYY-MM-DD)'
|
|
197
|
+
method_option :end_date, type: :string, desc: 'End date for custom range (YYYY-MM-DD)'
|
|
198
|
+
method_option :start_time, type: :string, desc: 'Start time for custom range (HH:MM)'
|
|
199
|
+
method_option :end_time, type: :string, desc: 'End time for custom range (HH:MM)'
|
|
200
|
+
method_option :limit, type: :numeric, default: 25,
|
|
201
|
+
desc: 'Limit the number of logs displayed (default: 25, use 0 for all)'
|
|
197
202
|
|
|
198
203
|
def logs(ip = nil)
|
|
199
204
|
# Use IP from options if not provided as argument
|
|
200
205
|
ip ||= options[:ip] || @config['ip']
|
|
201
206
|
with_connection(ip, options) do |conn|
|
|
202
207
|
# Get attendance logs
|
|
203
|
-
puts
|
|
208
|
+
puts 'Getting attendance logs...'
|
|
204
209
|
logs = conn.get_attendance_logs
|
|
205
210
|
total_logs = logs.size
|
|
206
211
|
puts "Total logs: #{total_logs}" if options[:verbose]
|
|
@@ -222,7 +227,8 @@ module RBZK
|
|
|
222
227
|
elsif options[:month]
|
|
223
228
|
today = Date.today
|
|
224
229
|
start_of_month = Date.new(today.year, today.month, 1)
|
|
225
|
-
logs = filter_logs_by_datetime(logs, start_of_month, today, options[:start_time],
|
|
230
|
+
logs = filter_logs_by_datetime(logs, start_of_month, today, options[:start_time],
|
|
231
|
+
options[:end_time])
|
|
226
232
|
"This Month's Attendance Logs (#{start_of_month} to #{today})"
|
|
227
233
|
elsif options[:start_date] && options[:end_date]
|
|
228
234
|
begin
|
|
@@ -233,11 +239,12 @@ module RBZK
|
|
|
233
239
|
puts "Filtering logs from #{start_date} to #{end_date}..." if options[:verbose]
|
|
234
240
|
|
|
235
241
|
# Use the filter_logs_by_datetime method
|
|
236
|
-
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
242
|
+
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
243
|
+
options[:end_time])
|
|
237
244
|
|
|
238
245
|
"Attendance Logs (#{start_date} to #{end_date})"
|
|
239
246
|
rescue ArgumentError
|
|
240
|
-
puts
|
|
247
|
+
puts 'Error: Invalid date format. Please use YYYY-MM-DD format.'
|
|
241
248
|
return
|
|
242
249
|
end
|
|
243
250
|
elsif options[:start_date]
|
|
@@ -249,11 +256,12 @@ module RBZK
|
|
|
249
256
|
puts "Filtering logs from #{start_date} onwards..." if options[:verbose]
|
|
250
257
|
|
|
251
258
|
# Use the filter_logs_by_datetime method
|
|
252
|
-
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
259
|
+
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
260
|
+
options[:end_time])
|
|
253
261
|
|
|
254
262
|
"Attendance Logs (#{start_date} to #{end_date})"
|
|
255
263
|
rescue ArgumentError
|
|
256
|
-
puts
|
|
264
|
+
puts 'Error: Invalid date format. Please use YYYY-MM-DD format.'
|
|
257
265
|
return
|
|
258
266
|
end
|
|
259
267
|
elsif options[:end_date]
|
|
@@ -266,15 +274,16 @@ module RBZK
|
|
|
266
274
|
puts "Filtering logs from #{start_date} to #{end_date}..." if options[:verbose]
|
|
267
275
|
|
|
268
276
|
# Use the filter_logs_by_datetime method
|
|
269
|
-
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
277
|
+
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
278
|
+
options[:end_time])
|
|
270
279
|
|
|
271
280
|
"Attendance Logs (#{start_date} to #{end_date})"
|
|
272
281
|
rescue ArgumentError
|
|
273
|
-
puts
|
|
282
|
+
puts 'Error: Invalid date format. Please use YYYY-MM-DD format.'
|
|
274
283
|
return
|
|
275
284
|
end
|
|
276
285
|
else
|
|
277
|
-
|
|
286
|
+
'All Attendance Logs'
|
|
278
287
|
end
|
|
279
288
|
|
|
280
289
|
# Display logs
|
|
@@ -288,15 +297,17 @@ module RBZK
|
|
|
288
297
|
if defined?(::Terminal) && defined?(::Terminal::Table)
|
|
289
298
|
# Pretty table output
|
|
290
299
|
table = ::Terminal::Table.new do |t|
|
|
291
|
-
t.title = title ||
|
|
292
|
-
t.headings = [ 'User ID', 'Time', 'Status' ]
|
|
300
|
+
t.title = title || 'Attendance Logs'
|
|
301
|
+
t.headings = ['UID', 'User ID', 'Time', 'Status', 'Punch Type']
|
|
293
302
|
|
|
294
303
|
# Show logs in the table based on limit
|
|
295
304
|
display_logs.each do |log|
|
|
296
305
|
t << [
|
|
306
|
+
log.uid,
|
|
297
307
|
log.user_id,
|
|
298
308
|
log.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
|
299
|
-
|
|
309
|
+
log.status.to_s,
|
|
310
|
+
log.punch_name
|
|
300
311
|
]
|
|
301
312
|
end
|
|
302
313
|
end
|
|
@@ -310,12 +321,10 @@ module RBZK
|
|
|
310
321
|
else
|
|
311
322
|
# Fallback plain text output
|
|
312
323
|
display_logs.each do |log|
|
|
313
|
-
puts " User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}"
|
|
324
|
+
puts " UID: #{log.uid}, User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}, Punch: #{log.punch_name}"
|
|
314
325
|
end
|
|
315
326
|
|
|
316
|
-
if logs.size > display_logs.size
|
|
317
|
-
puts " ... and #{logs.size - display_logs.size} more records"
|
|
318
|
-
end
|
|
327
|
+
puts " ... and #{logs.size - display_logs.size} more records" if logs.size > display_logs.size
|
|
319
328
|
end
|
|
320
329
|
else
|
|
321
330
|
puts "\nNo attendance records found"
|
|
@@ -323,26 +332,25 @@ module RBZK
|
|
|
323
332
|
end
|
|
324
333
|
end
|
|
325
334
|
|
|
326
|
-
desc
|
|
327
|
-
map
|
|
335
|
+
desc 'clear-logs [IP]', 'Clear attendance logs'
|
|
336
|
+
map 'clear-logs' => :clear_logs
|
|
328
337
|
|
|
329
338
|
def clear_logs(ip = nil)
|
|
330
|
-
|
|
331
339
|
# Use IP from options if not provided as argument
|
|
332
340
|
ip ||= options[:ip] || @config['ip']
|
|
333
341
|
with_connection(ip, options) do |conn|
|
|
334
|
-
puts
|
|
335
|
-
return unless yes?(
|
|
342
|
+
puts 'WARNING: This will delete all attendance logs from the device.'
|
|
343
|
+
return unless yes?('Are you sure you want to continue? (y/N)')
|
|
336
344
|
|
|
337
|
-
puts
|
|
345
|
+
puts 'Clearing attendance logs...'
|
|
338
346
|
result = conn.clear_attendance
|
|
339
|
-
puts
|
|
347
|
+
puts '✓ Attendance logs cleared successfully!' if result
|
|
340
348
|
end
|
|
341
349
|
end
|
|
342
350
|
|
|
343
|
-
desc
|
|
344
|
-
method_option :time, type: :numeric, default: 3, desc:
|
|
345
|
-
map
|
|
351
|
+
desc 'unlock [IP]', 'Unlock the door'
|
|
352
|
+
method_option :time, type: :numeric, default: 3, desc: 'Unlock time in seconds (default: 3)'
|
|
353
|
+
map 'unlock' => :unlock_door
|
|
346
354
|
|
|
347
355
|
def unlock_door(ip = nil)
|
|
348
356
|
# Use IP from options if not provided as argument
|
|
@@ -354,12 +362,12 @@ module RBZK
|
|
|
354
362
|
with_connection(ip, options) do |conn|
|
|
355
363
|
puts "Unlocking door for #{time} seconds..."
|
|
356
364
|
result = conn.unlock(time)
|
|
357
|
-
puts
|
|
365
|
+
puts '✓ Door unlocked successfully!' if result
|
|
358
366
|
end
|
|
359
367
|
end
|
|
360
368
|
|
|
361
|
-
desc
|
|
362
|
-
map
|
|
369
|
+
desc 'door-state [IP]', 'Get the door lock state'
|
|
370
|
+
map 'door-state' => :door_state
|
|
363
371
|
|
|
364
372
|
def door_state(ip = nil)
|
|
365
373
|
# Use IP from options if not provided as argument
|
|
@@ -371,8 +379,8 @@ module RBZK
|
|
|
371
379
|
end
|
|
372
380
|
end
|
|
373
381
|
|
|
374
|
-
desc
|
|
375
|
-
map
|
|
382
|
+
desc 'write-lcd [IP] LINE_NUMBER TEXT', 'Write text to LCD display'
|
|
383
|
+
map 'write-lcd' => :write_lcd
|
|
376
384
|
|
|
377
385
|
def write_lcd(line_number, text, ip = nil)
|
|
378
386
|
# Use IP from options if not provided as argument
|
|
@@ -384,59 +392,77 @@ module RBZK
|
|
|
384
392
|
with_connection(ip, options) do |conn|
|
|
385
393
|
puts "Writing text to LCD line #{line_number}..."
|
|
386
394
|
result = conn.write_lcd(line_number, text)
|
|
387
|
-
puts
|
|
395
|
+
puts '✓ Text written to LCD successfully!' if result
|
|
388
396
|
end
|
|
389
397
|
end
|
|
390
398
|
|
|
391
|
-
desc
|
|
392
|
-
map
|
|
399
|
+
desc 'clear-lcd [IP]', 'Clear the LCD display'
|
|
400
|
+
map 'clear-lcd' => :clear_lcd
|
|
393
401
|
|
|
394
402
|
def clear_lcd(ip = nil)
|
|
395
403
|
# Use IP from options if not provided as argument
|
|
396
404
|
ip ||= options[:ip] || @config['ip']
|
|
397
405
|
|
|
398
406
|
with_connection(ip, options) do |conn|
|
|
399
|
-
puts
|
|
407
|
+
puts 'Clearing LCD display...'
|
|
400
408
|
result = conn.clear_lcd
|
|
401
|
-
puts
|
|
409
|
+
puts '✓ LCD cleared successfully!' if result
|
|
402
410
|
end
|
|
403
411
|
end
|
|
404
412
|
|
|
405
|
-
desc
|
|
406
|
-
method_option :uid, type: :numeric, desc:
|
|
407
|
-
method_option :name, type: :string, default:
|
|
408
|
-
method_option :privilege, type: :numeric, default: 0, desc:
|
|
409
|
-
method_option :password, type: :string, default:
|
|
410
|
-
method_option :group_id, type: :string, default:
|
|
411
|
-
method_option :user_id, type: :string, default:
|
|
412
|
-
method_option :card, type: :numeric, default: 0, desc:
|
|
413
|
-
map
|
|
413
|
+
desc 'add-user [IP]', 'Add or update a user'
|
|
414
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device if not provided)'
|
|
415
|
+
method_option :name, type: :string, default: '', desc: 'User name'
|
|
416
|
+
method_option :privilege, type: :numeric, default: 0, desc: 'User privilege (0=User, 14=Admin)'
|
|
417
|
+
method_option :password, type: :string, default: '', desc: 'User password'
|
|
418
|
+
method_option :group_id, type: :string, default: '', desc: 'Group ID'
|
|
419
|
+
method_option :user_id, type: :string, default: '', desc: 'Custom user ID'
|
|
420
|
+
method_option :card, type: :numeric, default: 0, desc: 'Card number'
|
|
421
|
+
map 'add-user' => :add_user
|
|
414
422
|
|
|
415
423
|
def add_user(ip = nil)
|
|
416
424
|
# Use IP from options if not provided as argument
|
|
417
425
|
ip ||= options[:ip] || @config['ip']
|
|
418
426
|
|
|
419
427
|
with_connection(ip, options) do |conn|
|
|
420
|
-
puts
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
428
|
+
puts 'Adding/updating user...'
|
|
429
|
+
|
|
430
|
+
# Disable device for exclusive access during modification
|
|
431
|
+
begin
|
|
432
|
+
conn.disable_device
|
|
433
|
+
rescue StandardError
|
|
434
|
+
nil
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
begin
|
|
438
|
+
result = conn.set_user(
|
|
439
|
+
uid: options[:uid],
|
|
440
|
+
name: options[:name] || '',
|
|
441
|
+
privilege: options[:privilege] || 0,
|
|
442
|
+
password: options[:password] || '',
|
|
443
|
+
group_id: options[:group_id] || '',
|
|
444
|
+
user_id: options[:user_id] || '',
|
|
445
|
+
card: options[:card] || 0
|
|
446
|
+
)
|
|
447
|
+
puts '✓ User added/updated successfully!' if result
|
|
448
|
+
rescue RBZK::ZKError => e
|
|
449
|
+
# Reraise to let the with_connection handler print device messages
|
|
450
|
+
raise e
|
|
451
|
+
ensure
|
|
452
|
+
# Re-enable device after modification (always)
|
|
453
|
+
begin
|
|
454
|
+
conn.enable_device
|
|
455
|
+
rescue StandardError
|
|
456
|
+
nil
|
|
457
|
+
end
|
|
458
|
+
end
|
|
433
459
|
end
|
|
434
460
|
end
|
|
435
461
|
|
|
436
|
-
desc
|
|
437
|
-
method_option :uid, type: :numeric, desc:
|
|
438
|
-
method_option :user_id, type: :string, desc:
|
|
439
|
-
map
|
|
462
|
+
desc 'delete-user [IP]', 'Delete a user'
|
|
463
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device)'
|
|
464
|
+
method_option :user_id, type: :string, desc: 'Custom user ID'
|
|
465
|
+
map 'delete-user' => :delete_user
|
|
440
466
|
|
|
441
467
|
def delete_user(ip = nil)
|
|
442
468
|
# Use IP from options if not provided as argument
|
|
@@ -444,33 +470,33 @@ module RBZK
|
|
|
444
470
|
|
|
445
471
|
# Ensure at least one of uid or user_id is provided
|
|
446
472
|
if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
|
|
447
|
-
puts
|
|
473
|
+
puts 'Error: You must provide either --uid'
|
|
448
474
|
return
|
|
449
475
|
end
|
|
450
476
|
|
|
451
477
|
with_connection(ip, options) do |conn|
|
|
452
|
-
puts
|
|
478
|
+
puts 'Deleting user...'
|
|
453
479
|
|
|
454
480
|
# Use the User object with delete_user
|
|
455
481
|
result = conn.delete_user(uid: options[:uid])
|
|
456
482
|
|
|
457
483
|
if result
|
|
458
|
-
puts
|
|
484
|
+
puts '✓ User deleted successfully!'
|
|
459
485
|
else
|
|
460
|
-
puts
|
|
486
|
+
puts '✗ User not found or could not be deleted.'
|
|
461
487
|
end
|
|
462
488
|
end
|
|
463
489
|
end
|
|
464
490
|
|
|
465
|
-
desc
|
|
466
|
-
map
|
|
491
|
+
desc 'get-templates [IP]', 'Get all fingerprint templates'
|
|
492
|
+
map 'get-templates' => :get_templates
|
|
467
493
|
|
|
468
494
|
def get_templates(ip = nil)
|
|
469
495
|
# Use IP from options if not provided as argument
|
|
470
496
|
ip ||= options[:ip] || @config['ip']
|
|
471
497
|
|
|
472
498
|
with_connection(ip, options) do |conn|
|
|
473
|
-
puts
|
|
499
|
+
puts 'Getting fingerprint templates...'
|
|
474
500
|
templates = conn.get_templates
|
|
475
501
|
|
|
476
502
|
if templates && !templates.empty?
|
|
@@ -478,8 +504,8 @@ module RBZK
|
|
|
478
504
|
|
|
479
505
|
# Use Terminal::Table for pretty output
|
|
480
506
|
table = ::Terminal::Table.new do |t|
|
|
481
|
-
t.title =
|
|
482
|
-
t.headings = [
|
|
507
|
+
t.title = 'Fingerprint Templates'
|
|
508
|
+
t.headings = ['UID', 'Finger ID', 'Valid', 'Size']
|
|
483
509
|
|
|
484
510
|
templates.each do |template|
|
|
485
511
|
t << [
|
|
@@ -493,16 +519,16 @@ module RBZK
|
|
|
493
519
|
|
|
494
520
|
puts table
|
|
495
521
|
else
|
|
496
|
-
puts
|
|
522
|
+
puts '✓ No fingerprint templates found'
|
|
497
523
|
end
|
|
498
524
|
end
|
|
499
525
|
end
|
|
500
526
|
|
|
501
|
-
desc
|
|
502
|
-
method_option :uid, type: :numeric, desc:
|
|
503
|
-
method_option :user_id, type: :string, desc:
|
|
504
|
-
method_option :finger_id, type: :numeric, default: 0, desc:
|
|
505
|
-
map
|
|
527
|
+
desc 'get-user-template [IP]', "Get a specific user's fingerprint template"
|
|
528
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device)'
|
|
529
|
+
method_option :user_id, type: :string, desc: 'Custom user ID'
|
|
530
|
+
method_option :finger_id, type: :numeric, default: 0, desc: 'Finger ID (0-9)'
|
|
531
|
+
map 'get-user-template' => :get_user_template
|
|
506
532
|
|
|
507
533
|
def get_user_template(ip = nil)
|
|
508
534
|
# Use IP from options if not provided as argument
|
|
@@ -510,37 +536,37 @@ module RBZK
|
|
|
510
536
|
|
|
511
537
|
# Ensure at least one of uid or user_id is provided
|
|
512
538
|
if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
|
|
513
|
-
puts
|
|
539
|
+
puts 'Error: You must provide either --uid or --user-id'
|
|
514
540
|
return
|
|
515
541
|
end
|
|
516
542
|
|
|
517
543
|
with_connection(ip, options) do |conn|
|
|
518
|
-
puts
|
|
544
|
+
puts 'Getting user fingerprint template...'
|
|
519
545
|
|
|
520
546
|
# Extract parameters from options
|
|
521
547
|
uid = options[:uid] || 0
|
|
522
|
-
user_id = options[:user_id] ||
|
|
548
|
+
user_id = options[:user_id] || ''
|
|
523
549
|
finger_id = options[:finger_id] || 0
|
|
524
550
|
|
|
525
551
|
template = conn.get_user_template(uid: uid, temp_id: finger_id, user_id: user_id)
|
|
526
552
|
|
|
527
553
|
if template
|
|
528
|
-
puts
|
|
554
|
+
puts '✓ Found fingerprint template:'
|
|
529
555
|
puts " User ID: #{template.uid}"
|
|
530
556
|
puts " Finger ID: #{template.fid}"
|
|
531
557
|
puts " Valid: #{template.valid == 1 ? 'Yes' : 'No'}"
|
|
532
558
|
puts " Size: #{template.size} bytes"
|
|
533
559
|
else
|
|
534
|
-
puts
|
|
560
|
+
puts '✗ Fingerprint template not found'
|
|
535
561
|
end
|
|
536
562
|
end
|
|
537
563
|
end
|
|
538
564
|
|
|
539
|
-
desc
|
|
540
|
-
method_option :uid, type: :numeric, desc:
|
|
541
|
-
method_option :user_id, type: :string, desc:
|
|
542
|
-
method_option :finger_id, type: :numeric, default: 0, desc:
|
|
543
|
-
map
|
|
565
|
+
desc 'delete-template [IP]', 'Delete a specific fingerprint template'
|
|
566
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device)'
|
|
567
|
+
method_option :user_id, type: :string, desc: 'Custom user ID'
|
|
568
|
+
method_option :finger_id, type: :numeric, default: 0, desc: 'Finger ID (0-9)'
|
|
569
|
+
map 'delete-template' => :delete_template
|
|
544
570
|
|
|
545
571
|
def delete_template(ip = nil)
|
|
546
572
|
# Use IP from options if not provided as argument
|
|
@@ -548,31 +574,31 @@ module RBZK
|
|
|
548
574
|
|
|
549
575
|
# Ensure at least one of uid or user_id is provided
|
|
550
576
|
if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
|
|
551
|
-
puts
|
|
577
|
+
puts 'Error: You must provide either --uid or --user-id'
|
|
552
578
|
return
|
|
553
579
|
end
|
|
554
580
|
|
|
555
581
|
with_connection(ip, options) do |conn|
|
|
556
|
-
puts
|
|
582
|
+
puts 'Deleting fingerprint template...'
|
|
557
583
|
|
|
558
584
|
# Extract parameters from options
|
|
559
585
|
uid = options[:uid] || 0
|
|
560
|
-
user_id = options[:user_id] ||
|
|
586
|
+
user_id = options[:user_id] || ''
|
|
561
587
|
finger_id = options[:finger_id] || 0
|
|
562
588
|
|
|
563
589
|
result = conn.delete_user_template(uid: uid, temp_id: finger_id, user_id: user_id)
|
|
564
590
|
|
|
565
591
|
if result
|
|
566
|
-
puts
|
|
592
|
+
puts '✓ Fingerprint template deleted successfully!'
|
|
567
593
|
else
|
|
568
|
-
puts
|
|
594
|
+
puts '✗ Fingerprint template not found or could not be deleted'
|
|
569
595
|
end
|
|
570
596
|
end
|
|
571
597
|
end
|
|
572
598
|
|
|
573
|
-
desc
|
|
574
|
-
method_option :index, type: :numeric, desc:
|
|
575
|
-
map
|
|
599
|
+
desc 'test-voice [IP]', 'Test the device voice'
|
|
600
|
+
method_option :index, type: :numeric, desc: 'Sound index to play (0-35, default: 0)'
|
|
601
|
+
map 'test-voice' => :test_voice
|
|
576
602
|
|
|
577
603
|
def test_voice(ip = nil)
|
|
578
604
|
# Use IP from options if not provided as argument
|
|
@@ -583,89 +609,89 @@ module RBZK
|
|
|
583
609
|
|
|
584
610
|
# Print available sound indices if verbose
|
|
585
611
|
if options[:verbose]
|
|
586
|
-
puts
|
|
587
|
-
puts
|
|
588
|
-
puts
|
|
589
|
-
puts
|
|
590
|
-
puts
|
|
591
|
-
puts
|
|
592
|
-
puts
|
|
593
|
-
puts
|
|
594
|
-
puts
|
|
595
|
-
puts
|
|
596
|
-
puts
|
|
597
|
-
puts
|
|
598
|
-
puts
|
|
599
|
-
puts
|
|
600
|
-
puts
|
|
601
|
-
puts
|
|
602
|
-
puts
|
|
603
|
-
puts
|
|
604
|
-
puts
|
|
605
|
-
puts
|
|
606
|
-
puts
|
|
607
|
-
puts
|
|
608
|
-
puts
|
|
609
|
-
puts
|
|
610
|
-
puts
|
|
611
|
-
puts
|
|
612
|
-
puts
|
|
612
|
+
puts 'Available sound indices:'
|
|
613
|
+
puts ' 0: Thank You'
|
|
614
|
+
puts ' 1: Incorrect Password'
|
|
615
|
+
puts ' 2: Access Denied'
|
|
616
|
+
puts ' 3: Invalid ID'
|
|
617
|
+
puts ' 4: Please try again'
|
|
618
|
+
puts ' 5: Duplicate ID'
|
|
619
|
+
puts ' 6: The clock is flow'
|
|
620
|
+
puts ' 7: The clock is full'
|
|
621
|
+
puts ' 8: Duplicate finger'
|
|
622
|
+
puts ' 9: Duplicated punch'
|
|
623
|
+
puts '10: Beep kuko'
|
|
624
|
+
puts '11: Beep siren'
|
|
625
|
+
puts '13: Beep bell'
|
|
626
|
+
puts '18: Windows(R) opening sound'
|
|
627
|
+
puts '20: Fingerprint not emolt'
|
|
628
|
+
puts '21: Password not emolt'
|
|
629
|
+
puts '22: Badges not emolt'
|
|
630
|
+
puts '23: Face not emolt'
|
|
631
|
+
puts '24: Beep standard'
|
|
632
|
+
puts '30: Invalid user'
|
|
633
|
+
puts '31: Invalid time period'
|
|
634
|
+
puts '32: Invalid combination'
|
|
635
|
+
puts '33: Illegal Access'
|
|
636
|
+
puts '34: Disk space full'
|
|
637
|
+
puts '35: Duplicate fingerprint'
|
|
638
|
+
puts '51: Focus eyes on the green box'
|
|
613
639
|
end
|
|
614
640
|
|
|
615
641
|
with_connection(ip, options) do |conn|
|
|
616
642
|
puts "Testing device voice with index #{index}..."
|
|
617
643
|
result = conn.test_voice(index)
|
|
618
|
-
puts
|
|
644
|
+
puts '✓ Voice test successful!' if result
|
|
619
645
|
end
|
|
620
646
|
end
|
|
621
647
|
|
|
622
|
-
desc
|
|
648
|
+
desc 'restart [IP]', 'Restart the device'
|
|
623
649
|
|
|
624
650
|
def restart(ip = nil)
|
|
625
651
|
# Use IP from options if not provided as argument
|
|
626
652
|
ip ||= options[:ip] || @config['ip']
|
|
627
653
|
|
|
628
|
-
if yes?(
|
|
654
|
+
if yes?('Are you sure you want to restart the device? (y/N)')
|
|
629
655
|
with_connection(ip, options.merge(skip_disconnect_after_yield: true)) do |conn|
|
|
630
|
-
puts
|
|
656
|
+
puts 'Restarting device...'
|
|
631
657
|
result = conn.restart
|
|
632
|
-
puts
|
|
633
|
-
puts
|
|
658
|
+
puts '✓ Device restart command sent successfully!' if result
|
|
659
|
+
puts 'The device will restart now. You may need to wait a few moments before reconnecting.'
|
|
634
660
|
end
|
|
635
661
|
else
|
|
636
|
-
puts
|
|
662
|
+
puts 'Operation cancelled.'
|
|
637
663
|
end
|
|
638
664
|
end
|
|
639
665
|
|
|
640
|
-
desc
|
|
666
|
+
desc 'poweroff [IP]', 'Power off the device'
|
|
641
667
|
|
|
642
668
|
def poweroff(ip = nil)
|
|
643
669
|
# Use IP from options if not provided as argument
|
|
644
670
|
ip ||= options[:ip] || @config['ip']
|
|
645
671
|
|
|
646
|
-
if yes?(
|
|
672
|
+
if yes?('Are you sure you want to power off the device? (y/N)')
|
|
647
673
|
with_connection(ip, options.merge(skip_disconnect_after_yield: true)) do |conn|
|
|
648
|
-
puts
|
|
674
|
+
puts 'Powering off device...'
|
|
649
675
|
result = conn.poweroff
|
|
650
|
-
puts
|
|
651
|
-
puts
|
|
676
|
+
puts '✓ Device poweroff command sent successfully!' if result
|
|
677
|
+
puts 'The device will power off now. You will need to manually power it back on.'
|
|
652
678
|
end
|
|
653
679
|
else
|
|
654
|
-
puts
|
|
680
|
+
puts 'Operation cancelled.'
|
|
655
681
|
end
|
|
656
682
|
end
|
|
657
683
|
|
|
658
|
-
desc
|
|
684
|
+
desc 'config', 'Show current configuration'
|
|
659
685
|
|
|
660
686
|
def config
|
|
661
|
-
puts
|
|
662
|
-
puts
|
|
687
|
+
puts 'RBZK Configuration'
|
|
688
|
+
puts '=================='
|
|
663
689
|
@config.to_h.each do |key, value|
|
|
664
690
|
puts "#{key}: #{value}"
|
|
665
691
|
end
|
|
666
692
|
end
|
|
667
693
|
|
|
668
|
-
desc
|
|
694
|
+
desc 'config-set KEY VALUE', 'Set a configuration value'
|
|
669
695
|
|
|
670
696
|
def config_set(key, value)
|
|
671
697
|
# Convert value to appropriate type
|
|
@@ -683,16 +709,16 @@ module RBZK
|
|
|
683
709
|
puts "Configuration updated: #{key} = #{typed_value}"
|
|
684
710
|
end
|
|
685
711
|
|
|
686
|
-
desc
|
|
712
|
+
desc 'config-reset', 'Reset configuration to defaults'
|
|
687
713
|
|
|
688
714
|
def config_reset
|
|
689
|
-
if yes?(
|
|
715
|
+
if yes?('Are you sure you want to reset all configuration to defaults? (y/N)')
|
|
690
716
|
FileUtils.rm_f(@config.config_file)
|
|
691
717
|
@config = RBZK::CLI::Config.new
|
|
692
|
-
puts
|
|
718
|
+
puts 'Configuration reset to defaults.'
|
|
693
719
|
invoke :config
|
|
694
720
|
else
|
|
695
|
-
puts
|
|
721
|
+
puts 'Operation cancelled.'
|
|
696
722
|
end
|
|
697
723
|
end
|
|
698
724
|
|
|
@@ -708,7 +734,7 @@ module RBZK
|
|
|
708
734
|
skip_disconnect = local_opts.delete(:skip_disconnect_after_yield) || false
|
|
709
735
|
|
|
710
736
|
puts "Connecting to ZKTeco device at #{ip}:#{local_opts[:port] || @config['port'] || 4370}..." # Use local_opts
|
|
711
|
-
puts
|
|
737
|
+
puts 'Please ensure the device is powered on and connected to the network.'
|
|
712
738
|
|
|
713
739
|
conn = nil # Initialize conn for safety in ensure block
|
|
714
740
|
begin
|
|
@@ -727,29 +753,32 @@ module RBZK
|
|
|
727
753
|
conn = zk.connect
|
|
728
754
|
|
|
729
755
|
if conn.connected?
|
|
730
|
-
puts
|
|
756
|
+
puts '✓ Connected successfully!' unless local_opts[:quiet] # Use local_opts
|
|
731
757
|
yield conn if block_given?
|
|
732
758
|
else
|
|
733
|
-
puts
|
|
759
|
+
puts '✗ Failed to connect to device.'
|
|
734
760
|
end
|
|
735
761
|
rescue RBZK::ZKNetworkError => e
|
|
736
762
|
puts "✗ Network Error: #{e.message}"
|
|
737
|
-
puts
|
|
763
|
+
puts 'Please check the IP address and ensure the device is reachable.'
|
|
764
|
+
rescue RBZK::ZKErrorExists => e
|
|
765
|
+
puts "✗ Device Error: #{e.message}"
|
|
738
766
|
rescue RBZK::ZKErrorResponse => e
|
|
739
767
|
puts "✗ Device Error: #{e.message}"
|
|
740
|
-
puts
|
|
741
|
-
rescue => e
|
|
768
|
+
puts 'The device returned an error response.'
|
|
769
|
+
rescue StandardError => e
|
|
742
770
|
puts "✗ Unexpected Error: #{e.message}"
|
|
743
|
-
puts
|
|
771
|
+
puts 'An unexpected error occurred while communicating with the device.'
|
|
744
772
|
puts e.backtrace.join("\n") if local_opts[:verbose] # Use local_opts
|
|
745
773
|
ensure
|
|
746
774
|
if conn && conn.connected?
|
|
747
775
|
if skip_disconnect
|
|
748
|
-
|
|
776
|
+
# Use local_opts
|
|
777
|
+
puts 'Skipping disconnect as device is undergoing restart/poweroff.' unless local_opts[:quiet]
|
|
749
778
|
else
|
|
750
|
-
puts
|
|
779
|
+
puts 'Disconnecting from device...' unless local_opts[:quiet] # Use local_opts
|
|
751
780
|
conn.disconnect
|
|
752
|
-
puts
|
|
781
|
+
puts '✓ Disconnected' unless local_opts[:quiet] # Use local_opts
|
|
753
782
|
end
|
|
754
783
|
end
|
|
755
784
|
end
|
|
@@ -761,8 +790,8 @@ module RBZK
|
|
|
761
790
|
|
|
762
791
|
# Use Terminal::Table for pretty output
|
|
763
792
|
table = ::Terminal::Table.new do |t|
|
|
764
|
-
t.title =
|
|
765
|
-
t.headings = [
|
|
793
|
+
t.title = 'Users'
|
|
794
|
+
t.headings = ['UID', 'User ID', 'Name', 'Privilege', 'Password', 'Group ID', 'Card']
|
|
766
795
|
|
|
767
796
|
users.each do |user|
|
|
768
797
|
t << [
|
|
@@ -770,7 +799,7 @@ module RBZK
|
|
|
770
799
|
user.user_id,
|
|
771
800
|
user.name,
|
|
772
801
|
format_privilege(user.privilege),
|
|
773
|
-
|
|
802
|
+
user.password.nil? || user.password.empty? ? '(none)' : '(set)',
|
|
774
803
|
user.group_id,
|
|
775
804
|
user.card
|
|
776
805
|
]
|
|
@@ -779,7 +808,7 @@ module RBZK
|
|
|
779
808
|
|
|
780
809
|
puts table
|
|
781
810
|
else
|
|
782
|
-
puts
|
|
811
|
+
puts '✓ No users found'
|
|
783
812
|
end
|
|
784
813
|
end
|
|
785
814
|
|
|
@@ -799,32 +828,17 @@ module RBZK
|
|
|
799
828
|
log.timestamp >= start_datetime && log.timestamp <= end_datetime
|
|
800
829
|
end
|
|
801
830
|
|
|
802
|
-
if options[:verbose]
|
|
803
|
-
puts "Filtered logs: #{filtered_logs.size} of #{logs.size}"
|
|
804
|
-
end
|
|
831
|
+
puts "Filtered logs: #{filtered_logs.size} of #{logs.size}" if options[:verbose]
|
|
805
832
|
|
|
806
833
|
filtered_logs
|
|
807
834
|
end
|
|
808
835
|
|
|
809
|
-
def format_status(status)
|
|
810
|
-
case status
|
|
811
|
-
when 0 then "Check In"
|
|
812
|
-
when 1 then "Check Out"
|
|
813
|
-
when 2 then "Break Out"
|
|
814
|
-
when 3 then "Break In"
|
|
815
|
-
when 4 then "Overtime In"
|
|
816
|
-
when 5 then "Overtime Out"
|
|
817
|
-
else "Unknown (#{status})"
|
|
818
|
-
end
|
|
819
|
-
end
|
|
820
|
-
|
|
821
836
|
def format_privilege(privilege)
|
|
822
837
|
case privilege
|
|
823
|
-
when
|
|
824
|
-
when
|
|
825
|
-
when
|
|
826
|
-
when
|
|
827
|
-
when 14 then "Super Admin"
|
|
838
|
+
when RBZK::Constants::USER_DEFAULT then 'User'
|
|
839
|
+
when RBZK::Constants::USER_ENROLLER then 'Enroller'
|
|
840
|
+
when RBZK::Constants::USER_MANAGER then 'Manager'
|
|
841
|
+
when RBZK::Constants::USER_ADMIN then 'Admin'
|
|
828
842
|
else "Unknown (#{privilege})"
|
|
829
843
|
end
|
|
830
844
|
end
|