Initial commit: ZPL-native PDF printing, drop CUPS dependency
This commit is contained in:
commit
2ea45b5f21
15 files changed
+626
No files matched your search
@@ -0,0 +1,3 @@
|
||||
.session_secret
|
||||
public/files/*
|
||||
!public/files/.gitkeep
|
||||
@@ -0,0 +1,10 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'sinatra'
|
||||
gem 'sinatra-contrib'
|
||||
gem 'puma'
|
||||
gem 'rqrcode'
|
||||
gem 'logger'
|
||||
gem 'ostruct'
|
||||
|
||||
gem 'rackup', "~> 2.1"
|
||||
@@ -0,0 +1,56 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
base64 (0.3.0)
|
||||
chunky_png (1.4.0)
|
||||
logger (1.7.0)
|
||||
multi_json (1.21.1)
|
||||
mustermann (3.1.1)
|
||||
nio4r (2.7.5)
|
||||
ostruct (0.6.3)
|
||||
puma (8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
rack (3.2.6)
|
||||
rack-protection (4.2.1)
|
||||
base64 (>= 0.1.0)
|
||||
logger (>= 1.6.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
rack-session (2.1.2)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0)
|
||||
rackup (2.3.1)
|
||||
rack (>= 3)
|
||||
rqrcode (3.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 2.0)
|
||||
rqrcode_core (2.1.0)
|
||||
sinatra (4.2.1)
|
||||
logger (>= 1.6.0)
|
||||
mustermann (~> 3.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
rack-protection (= 4.2.1)
|
||||
rack-session (>= 2.0.0, < 3)
|
||||
tilt (~> 2.0)
|
||||
sinatra-contrib (4.2.1)
|
||||
multi_json (>= 0.0.2)
|
||||
mustermann (~> 3.0)
|
||||
rack-protection (= 4.2.1)
|
||||
sinatra (= 4.2.1)
|
||||
tilt (~> 2.0)
|
||||
tilt (2.7.0)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
armv7l-linux-eabihf
|
||||
|
||||
DEPENDENCIES
|
||||
logger
|
||||
ostruct
|
||||
puma
|
||||
rackup (~> 2.1)
|
||||
rqrcode
|
||||
sinatra
|
||||
sinatra-contrib
|
||||
|
||||
BUNDLED WITH
|
||||
4.0.14
|
||||
@@ -0,0 +1,138 @@
|
||||
# /usr/bin/env ruby
|
||||
require 'sinatra'
|
||||
require "sinatra/reloader"
|
||||
require 'tmpdir'
|
||||
|
||||
set :protection, :except => :frame_options
|
||||
set :environment, :production
|
||||
set :bind, "::"
|
||||
set :port, 4567
|
||||
|
||||
if !File.exist?('config.json') || !File.readable?('config.json')
|
||||
raise RuntimeException, 'Could not read config.json'
|
||||
end
|
||||
|
||||
PRINTER_DEVICE = '/dev/usb/lp0'
|
||||
PRINTER_DPI = 203 # DatamaxONeil E4204B Mark III native head resolution
|
||||
PRINTER_DARKNESS = 8 # lower than factory default to stop thermal dot-gain from fusing barcode bars
|
||||
TEST_LABEL_PDF = File.join(File.dirname(__FILE__), 'test-label.pdf')
|
||||
|
||||
configure do
|
||||
enable :static
|
||||
set :session_secret, File.read(File.join(File.dirname(__FILE__), '.session_secret')).strip
|
||||
enable :sessions
|
||||
|
||||
set :views, File.join(File.dirname(__FILE__), 'views')
|
||||
set :public_folder, File.join(File.dirname(__FILE__), 'public')
|
||||
set :files, File.join(settings.public_folder, 'files')
|
||||
set :unallowed_paths, ['.', '..']
|
||||
end
|
||||
|
||||
configure :production do
|
||||
enable :reloader
|
||||
end
|
||||
|
||||
helpers do
|
||||
def flash(message = '', level)
|
||||
case level
|
||||
when "success"
|
||||
session[:alert_success] = message
|
||||
when "danger"
|
||||
session[:alert_danger] = message
|
||||
when "warning"
|
||||
session[:alert_warning] = message
|
||||
end
|
||||
end
|
||||
|
||||
# Renders a PDF to ZPL ^GFA graphic labels at the printer's native DPI,
|
||||
# bypassing CUPS/Gutenprint's raster pipeline which blurs text and 1px-shifts
|
||||
# output compared to native ZPL rendering.
|
||||
def pdf_to_zpl(pdf_path, dpi: PRINTER_DPI)
|
||||
Dir.mktmpdir do |dir|
|
||||
pattern = File.join(dir, 'page-%03d.pbm')
|
||||
ok = system('gs', '-q', '-dNOPAUSE', '-dBATCH', '-dSAFER',
|
||||
'-sDEVICE=pbmraw', "-r#{dpi}",
|
||||
'-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
|
||||
"-sOutputFile=#{pattern}", pdf_path)
|
||||
raise 'ghostscript rendering failed' unless ok
|
||||
|
||||
Dir.glob(File.join(dir, 'page-*.pbm')).sort.map { |f| pbm_to_zpl_label(f) }.join
|
||||
end
|
||||
end
|
||||
|
||||
def pbm_to_zpl_label(pbm_path)
|
||||
bytes = File.binread(pbm_path)
|
||||
header = /\AP4\s+(?:#[^\n]*\n\s*)*(\d+)\s+(\d+)\s/.match(bytes)
|
||||
raise "unreadable PBM header in #{pbm_path}" unless header
|
||||
|
||||
width, height = header[1].to_i, header[2].to_i
|
||||
bytes_per_row = (width + 7) / 8
|
||||
total_bytes = bytes_per_row * height
|
||||
body = bytes.byteslice(header.end(0), total_bytes)
|
||||
hexdata = body.unpack1('H*').upcase
|
||||
|
||||
"^XA\n^MD#{PRINTER_DARKNESS}\n^PW#{width}\n^LL#{height}\n^FO0,0^GFA,#{total_bytes},#{total_bytes},#{bytes_per_row},#{hexdata}\n^XZ\n"
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
@alert_success = session.delete(:alert_success)
|
||||
@alert_danger = session.delete(:alert_danger)
|
||||
@alert_warning = session.delete(:alert_warning)
|
||||
end
|
||||
|
||||
|
||||
get '/' do
|
||||
erb :index
|
||||
end
|
||||
|
||||
get '/upload' do
|
||||
erb :upload
|
||||
end
|
||||
|
||||
post '/upload' do
|
||||
if params[:file]
|
||||
|
||||
tempfile = params[:file][:tempfile]
|
||||
filename = params[:file][:filename]
|
||||
filetype = File.extname filename
|
||||
filepath = File.join(settings.files, filename)
|
||||
|
||||
File.open(filepath, 'wb') do |f|
|
||||
f.write tempfile.read
|
||||
end
|
||||
|
||||
case filetype
|
||||
when ".pdf"
|
||||
begin
|
||||
zpl = pdf_to_zpl(filepath)
|
||||
File.open(PRINTER_DEVICE, 'wb') { |f| f.write(zpl) }
|
||||
flash "PDF: Upload successful (#{filename})", "success"
|
||||
rescue => e
|
||||
flash "PDF: Failed to print (#{e.message})", "danger"
|
||||
end
|
||||
when ".zpl"
|
||||
File.open(PRINTER_DEVICE, 'wb') { |f| f.write(File.binread(filepath)) }
|
||||
flash "ZPL: Upload successful (#{filename})", "success"
|
||||
else
|
||||
flash "#{filetype} is not supported. Only .pdf and .zpl!", "false"
|
||||
end
|
||||
|
||||
File.delete(filepath) if File.exist?(filepath)
|
||||
else
|
||||
flash 'You have to choose a file', "warning"
|
||||
end
|
||||
|
||||
redirect '/upload'
|
||||
end
|
||||
|
||||
post '/test-print' do
|
||||
begin
|
||||
zpl = pdf_to_zpl(TEST_LABEL_PDF)
|
||||
File.open(PRINTER_DEVICE, 'wb') { |f| f.write(zpl) }
|
||||
flash 'Test label sent to printer', 'success'
|
||||
rescue => e
|
||||
flash "Test print failed (#{e.message})", 'danger'
|
||||
end
|
||||
redirect '/upload'
|
||||
end
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"printers":[
|
||||
{
|
||||
"uri": "file:///dev/usb/lp0",
|
||||
"model": "E4204B",
|
||||
"dimensions": "100x150",
|
||||
"name": "Datamax-ONeil_E4204B_Mark_III",
|
||||
"driver": "zpl"
|
||||
},
|
||||
{
|
||||
"uri": "file:///dev/usb/lp1",
|
||||
"model": "QL-700",
|
||||
"dimensions": "62",
|
||||
"name": "Brother QL-700",
|
||||
"driver": "brother_ql"
|
||||
},
|
||||
{
|
||||
"uri": "file:///dev/usb/lp2",
|
||||
"model": "QL-1100",
|
||||
"dimensions": "102",
|
||||
"name": "Brother QL-1100",
|
||||
"driver": "brother_ql"
|
||||
},
|
||||
{
|
||||
"uri": "file:///dev/usb/lp3",
|
||||
"model": "QL-560",
|
||||
"dimensions": "12+19",
|
||||
"name": "Brother QL-560",
|
||||
"driver": "brother_ql"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
require 'bundler'
|
||||
|
||||
Bundler.require
|
||||
|
||||
require './app'
|
||||
run Sinatra::Application
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
# gemset.gem.gems generated gem export file. Note that any env variable settings will be missing. Append these after using a ';' field separator
|
||||
|
||||
error_highlight -v0.3.0
|
||||
did_you_mean -v1.6.1
|
||||
abbrev -v0.1.0
|
||||
awesome_print -v1.9.2
|
||||
base64 -v0.1.1
|
||||
benchmark -v0.2.0
|
||||
bigdecimal -v3.1.2
|
||||
bigdecimal -v3.1.1
|
||||
bundler -v2.3.10
|
||||
bundler -v2.3.6
|
||||
cgi -v0.3.2
|
||||
cgi -v0.3.1
|
||||
chunky_png -v1.4.0
|
||||
csv -v3.2.2
|
||||
cupsffi -v0.1.9
|
||||
date -v3.2.2
|
||||
delegate -v0.2.0
|
||||
digest -v3.1.0
|
||||
domain_name -v0.5.20190701
|
||||
drb -v2.1.0
|
||||
english -v0.7.1
|
||||
erb -v2.2.3
|
||||
etc -v1.3.0
|
||||
fcntl -v1.0.1
|
||||
ffi -v1.15.5
|
||||
fiddle -v1.1.0
|
||||
fileutils -v1.6.0
|
||||
find -v0.1.1
|
||||
forwardable -v1.3.2
|
||||
getoptlong -v0.1.1
|
||||
http-accept -v2.1.1
|
||||
http-accept -v1.7.0
|
||||
http-cookie -v1.0.4
|
||||
io-console -v0.5.11
|
||||
io-console -v0.5.10
|
||||
io-nonblock -v0.1.0
|
||||
io-wait -v0.2.1
|
||||
ipaddr -v1.2.4
|
||||
ipaddr -v1.2.3
|
||||
irb -v1.4.1
|
||||
json -v2.6.1
|
||||
logger -v1.5.1
|
||||
logger -v1.5.0
|
||||
mime-types -v3.4.1
|
||||
mime-types-data -v3.2022.0105
|
||||
multi_json -v1.15.0
|
||||
mustermann -v2.0.2
|
||||
mustermann -v1.1.1
|
||||
mutex_m -v0.1.1
|
||||
net-http -v0.2.0
|
||||
net-imap -v0.2.3
|
||||
net-protocol -v0.1.2
|
||||
netrc -v0.11.0
|
||||
nio4r -v2.5.8
|
||||
nkf -v0.1.1
|
||||
observer -v0.1.1
|
||||
open-uri -v0.2.0
|
||||
open3 -v0.1.1
|
||||
openssl -v3.0.0
|
||||
optparse -v0.2.0
|
||||
ostruct -v0.5.4
|
||||
ostruct -v0.5.3
|
||||
ostruct -v0.5.2
|
||||
pathname -v0.2.0
|
||||
pp -v0.3.0
|
||||
prettyprint -v0.1.1
|
||||
pstore -v0.1.1
|
||||
psych -v4.0.3
|
||||
puma -v5.6.5
|
||||
puma -v5.6.2
|
||||
puma -v5.6.1
|
||||
racc -v1.6.0
|
||||
rack -v2.2.4
|
||||
rack -v2.2.3
|
||||
rack-protection -v2.2.2
|
||||
rack-protection -v2.2.0
|
||||
rack-protection -v2.1.0
|
||||
rbs -v2.2.2
|
||||
rbs -v2.1.0
|
||||
rdoc -v6.4.0
|
||||
readline -v0.0.3
|
||||
readline-ext -v0.1.4
|
||||
reline -v0.3.1
|
||||
reline -v0.3.0
|
||||
resolv -v0.2.1
|
||||
resolv-replace -v0.1.0
|
||||
rest-client -v2.1.0
|
||||
rinda -v0.1.1
|
||||
rqrcode -v2.1.2
|
||||
rqrcode -v2.1.1
|
||||
rqrcode -v2.1.0
|
||||
rqrcode_core -v1.2.0
|
||||
ruby2_keywords -v0.0.5
|
||||
rubygems-update -v3.3.10
|
||||
rubygems-update -v3.3.6
|
||||
securerandom -v0.2.0
|
||||
securerandom -v0.1.1
|
||||
set -v1.0.2
|
||||
shellwords -v0.1.0
|
||||
sinatra -v2.2.2
|
||||
sinatra -v2.2.0
|
||||
sinatra -v2.1.0
|
||||
sinatra-contrib -v2.2.2
|
||||
sinatra-contrib -v2.2.0
|
||||
sinatra-contrib -v2.1.0
|
||||
singleton -v0.1.1
|
||||
stringio -v3.0.1
|
||||
strscan -v3.0.1
|
||||
syslog -v0.1.0
|
||||
tempfile -v0.1.2
|
||||
tilt -v2.0.11
|
||||
tilt -v2.0.10
|
||||
time -v0.2.0
|
||||
timeout -v0.2.0
|
||||
tmpdir -v0.1.2
|
||||
tsort -v0.1.0
|
||||
typeprof -v0.21.2
|
||||
un -v0.2.0
|
||||
unf -v0.1.4
|
||||
unf_ext -v0.0.8.1
|
||||
unf_ext -v0.0.8
|
||||
uri -v0.11.0
|
||||
weakref -v0.1.1
|
||||
yaml -v0.2.0
|
||||
zlib -v2.1.1
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
applications:
|
||||
- name: bootstrap
|
||||
memory: 128M
|
||||
instances: 1
|
||||
url: bootstrap.${target-base}
|
||||
path: .
|
||||
Whitespace-only changes.
Binary file not shown.
@@ -0,0 +1,115 @@
|
||||
require 'tmpdir'
|
||||
require 'rqrcode'
|
||||
|
||||
DPI = 203
|
||||
SRC_PDF = ARGV[0] || '/home/randy/EPH266757312_adresne_stitky_a6.pdf'
|
||||
OUT_PS = ARGV[1] || '/tmp/test-label-v2.ps'
|
||||
|
||||
# barcode crop region in the source label, in native pixels at 203dpi
|
||||
BARCODE_BOX = { x0: 388, y0: 222, x1: 772, y1: 322 }
|
||||
|
||||
def read_pbm(path)
|
||||
bytes = File.binread(path)
|
||||
header = /\AP4\s+(?:#[^\n]*\n\s*)*(\d+)\s+(\d+)\s/.match(bytes)
|
||||
raise "bad pbm #{path}" unless header
|
||||
|
||||
width, height = header[1].to_i, header[2].to_i
|
||||
bytes_per_row = (width + 7) / 8
|
||||
body = bytes.byteslice(header.end(0), bytes_per_row * height)
|
||||
{ width: width, height: height, bytes_per_row: bytes_per_row, body: body }
|
||||
end
|
||||
|
||||
def pbm_bit(pbm, x, y)
|
||||
byte = pbm[:body].getbyte(y * pbm[:bytes_per_row] + (x / 8))
|
||||
(byte >> (7 - (x % 8))) & 1
|
||||
end
|
||||
|
||||
# Packs a [x,y] => 0/1 (1 = black) pixel getter into row-padded, bit-inverted
|
||||
# (PostScript DeviceGray 1bpc: 0=black) bytes ready for the `image` operator.
|
||||
def pack_for_ps_image(width, height)
|
||||
bytes_per_row = (width + 7) / 8
|
||||
rows = Array.new(height) { "\x00".b * bytes_per_row }
|
||||
height.times do |y|
|
||||
row = rows[y].dup.b
|
||||
width.times do |x|
|
||||
next unless yield(x, y) == 1
|
||||
|
||||
byte_index = x / 8
|
||||
bit = 0x80 >> (x % 8)
|
||||
row.setbyte(byte_index, row.getbyte(byte_index) | bit)
|
||||
end
|
||||
rows[y] = row
|
||||
end
|
||||
packed = rows.join
|
||||
inverted = packed.bytes.map { |b| (~b) & 0xFF }.pack('C*')
|
||||
{ width: width, height: height, hex: inverted.unpack1('H*').upcase }
|
||||
end
|
||||
|
||||
def ps_image_block(x_pt, y_pt, w_pt, h_pt, img)
|
||||
<<~PS
|
||||
gsave
|
||||
#{x_pt} #{y_pt} translate
|
||||
#{w_pt} #{h_pt} scale
|
||||
#{img[:width]} #{img[:height]} 1
|
||||
[#{img[:width]} 0 0 -#{img[:height]} 0 #{img[:height]}]
|
||||
{ <#{img[:hex]}> } image
|
||||
grestore
|
||||
PS
|
||||
end
|
||||
|
||||
Dir.mktmpdir do |dir|
|
||||
pbm_path = File.join(dir, 'label.pbm')
|
||||
system('gs', '-q', '-dNOPAUSE', '-dBATCH', '-dSAFER',
|
||||
'-sDEVICE=pbmraw', "-r#{DPI}", '-dFirstPage=1', '-dLastPage=1',
|
||||
"-sOutputFile=#{pbm_path}", SRC_PDF) or raise 'gs failed'
|
||||
|
||||
pbm = read_pbm(pbm_path)
|
||||
bw = BARCODE_BOX[:x1] - BARCODE_BOX[:x0]
|
||||
bh = BARCODE_BOX[:y1] - BARCODE_BOX[:y0]
|
||||
barcode_img = pack_for_ps_image(bw, bh) do |x, y|
|
||||
pbm_bit(pbm, BARCODE_BOX[:x0] + x, BARCODE_BOX[:y0] + y)
|
||||
end
|
||||
|
||||
qr = RQRCode::QRCode.new('TEST-LABEL-PRINTSERVER')
|
||||
modules = qr.modules
|
||||
quiet = 4
|
||||
qsize = modules.size + quiet * 2
|
||||
qr_img = pack_for_ps_image(qsize, qsize) do |x, y|
|
||||
mx, my = x - quiet, y - quiet
|
||||
next 0 if mx.negative? || my.negative? || mx >= modules.size || my >= modules.size
|
||||
|
||||
modules[my][mx] ? 1 : 0
|
||||
end
|
||||
|
||||
# physical sizes on the A6 (298x420pt) page
|
||||
barcode_pt_w = 220
|
||||
barcode_pt_h = barcode_pt_w * bh / bw
|
||||
qr_pt = 100
|
||||
|
||||
ps = <<~PS
|
||||
%!PS
|
||||
<< /PageSize [298 420] >> setpagedevice
|
||||
1 setlinewidth
|
||||
10 10 278 400 rectstroke
|
||||
/Helvetica-Bold findfont 24 scalefont setfont
|
||||
30 380 moveto (TEST LABEL) show
|
||||
/Helvetica findfont 9 scalefont setfont
|
||||
30 362 moveto (Generic print-quality test page - safe to reprint anytime) show
|
||||
/Helvetica-Bold findfont 13 scalefont setfont
|
||||
30 330 moveto (Odosielatel: Test Sender, BA) show
|
||||
30 312 moveto (Adresat: Test Recipient, KE) show
|
||||
/Helvetica findfont 8 scalefont setfont
|
||||
30 292 moveto (Fine print 8pt: quick brown fox 0123456789) show
|
||||
#{ps_image_block(30, 210, barcode_pt_w, barcode_pt_h, barcode_img)}
|
||||
/Helvetica findfont 8 scalefont setfont
|
||||
30 198 moveto (1D: reused known-good Code128 from a real courier label) show
|
||||
#{ps_image_block(30, 60, qr_pt, qr_pt, qr_img)}
|
||||
/Helvetica findfont 8 scalefont setfont
|
||||
140 100 moveto (QR: TEST-LABEL-PRINTSERVER) show
|
||||
140 88 moveto (rendered natively via rqrcode) show
|
||||
showpage
|
||||
PS
|
||||
|
||||
File.write(OUT_PS, ps)
|
||||
puts "wrote #{OUT_PS}"
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Brother Label Designer</h5>
|
||||
<p class="card-text">A web service to print labels on Brother QL label printers.</p>
|
||||
<a href="http://rpi-printserver.local:8560/labeldesigner" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">QL-560</a>
|
||||
<a href="http://rpi-printserver.local:8700/labeldesigner" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">QL-700</a>
|
||||
<a href="http://rpi-printserver.local:9100/labeldesigner" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">QL-1100</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Sticker Factory</h5>
|
||||
<p class="card-text">Print images, text and cats on a Brother QL label printer</p>
|
||||
<a href="http://rpi-printserver.local:8989" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Cups</h5>
|
||||
<p class="card-text">The standards-based, open source printing system developed by Apple for iOS®, iPadOS®, and macOS®. CUPS uses IPP Everywhere™ to support printing to local and network printers.</p>
|
||||
<a href="https://rpi-printserver.local:631/admin" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Cockpit</h5>
|
||||
<p class="card-text">Cockpit is a web-based graphical interface for servers, intended for everyone</p>
|
||||
<a href="https://rpi-printserver.local:9090" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">ZPL Viewer</h5>
|
||||
<p class="card-text">An useful tool for labels in ZPL format.</p>
|
||||
<a href="https://labelary.com/viewer.html" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Go</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Creality Sonic Pad</h5>
|
||||
<p class="card-text">3D Printer</p>
|
||||
<a href="http://spad-9088.local" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Fluidd</a>
|
||||
<a href="http://spad-9088.local:8819" class="btn btn-outline-secondary" target="_blank" rel="noopener noreferrer">Mainsail</a>
|
||||
<a href="http://spad-9088.local:7125" class="btn btn-outline-secondary" target="_blank" rel="noopener noreferrer">Moonraker</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bigtreetech Pad 7 CNC</h5>
|
||||
<p class="card-text">CNC</p>
|
||||
<a href="http://btt-pad7-cnc.local" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Mainsail</a>
|
||||
<a href="http://btt-pad7-cnc.local:7125" class="btn btn-outline-secondary" target="_blank" rel="noopener noreferrer">Moonraker</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bigtreetech Pad 7 CAN</h5>
|
||||
<p class="card-text">CAN</p>
|
||||
<a href="http://rpi-btt-pad7-can.local" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Mainsail</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Print server</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAD9/f0AAAAAAOzs7ADl5eUA/Pz8ACsZBgD+/v4AAwMDAPv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWZmZmZmIxERZmZmZmZmERFmZmZmZmYREWZmZoZmZhERZmYUVmZmERFmYREWZmYREWYRERFmZhERZhYREXZmERFmZmYRZmYREWZmZhFmZhERZmZmERFmERFmZmYHEWYREWZmZmZmZhERZmZmZmZmERFmZmZmZmYREWZmZmZmZhHAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAAwAMAAMADAADAAwAA" rel="icon" type="image/x-icon" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Print server</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavDropdown">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/upload">Shipping labels</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<% if @alert_success then %>
|
||||
<div class="alert alert-success"><%=@alert_success%></div>
|
||||
<% end %>
|
||||
<% if @alert_danger then %>
|
||||
<div class="alert alert-danger"><%=@alert_danger%></div>
|
||||
<% end %>
|
||||
<% if @alert_warning then %>
|
||||
<div class="alert alert-warning"><%=@alert_warning%></div>
|
||||
<% end %>
|
||||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone-min.js"></script>
|
||||
<script>
|
||||
Dropzone.options.UploadForm = {
|
||||
paramFile: "file",
|
||||
accept: function(file, done) {
|
||||
done ();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<form action='/login/attempt' method='post' class="form-inline well">
|
||||
<input type="text" name='username' placeholder="username" autofocus>
|
||||
<input class="btn primary" type='submit' value='log in'>
|
||||
</form>
|
||||
@@ -0,0 +1,11 @@
|
||||
<form action="/upload" class="dropzone" id="upload-form" method="POST" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="formFile" class="form-label">Upload .pdf or .zpl to print label</label>
|
||||
<input name="file" class="form-control" type="file" id="formFile" accept=".pdf,.zpl" />
|
||||
</div>
|
||||
<button class="btn btn-secondary" type="submit">Upload</button>
|
||||
</form>
|
||||
|
||||
<form action="/test-print" method="POST" class="mt-3">
|
||||
<button class="btn btn-outline-secondary" type="submit">Print test label</button>
|
||||
</form>
|
||||
Reference in new issue
Block a user