Compare commits
12
Commits
f09c92f201
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc59cf595e | ||
|
|
7ea03e5bc5 | ||
|
|
61d190ed91 | ||
|
|
46fd345b7f | ||
|
|
4308850a2e | ||
|
|
a58d4b66ea | ||
|
|
ec9278c0dd | ||
|
|
f351212395 | ||
|
|
eafea9f728 | ||
|
|
f8857384fd | ||
|
|
d2900361b3 | ||
|
|
790d716d57 |
No files matched your search
+31
-6
@@ -1,18 +1,43 @@
|
||||
= labelprint
|
||||
|
||||
Unified UI for printing labels/stickers:
|
||||
Unified UI for printing labels/receipts:
|
||||
|
||||
* Native ZPL printing to a Datamax-ONeil thermal barcode printer
|
||||
* A proxy/switcher in front of three https://github.com/DL6ER/brother_ql_web[brother_ql_web] instances, one per Brother QL printer
|
||||
* ESC/POS receipt printing to an Epson TM-T20II
|
||||
|
||||
== Routes
|
||||
|
||||
* `/` -- upload a PDF and print it (ZPL)
|
||||
* `/upload` -- upload form
|
||||
* `/test-print` -- print the bundled test label
|
||||
* `/` -- home page, links to everything below
|
||||
* `/upload`, `POST /upload` -- upload a PDF/ZPL file and print it (Datamax)
|
||||
* `POST /shipping/zpl` -- print raw ZPL text (see "API usage" below)
|
||||
* `/test-print` -- print the bundled Datamax test label
|
||||
* `/epson` -- receipt printing UI (title/body/QR template, or raw ESC/POS mnemonic notation)
|
||||
* `POST /epson/print` -- print a title/body/QR receipt
|
||||
* `POST /epson/raw` -- print already-formatted ESC/POS mnemonic notation (see "API usage" below)
|
||||
* `POST /epson/test-print` -- print an Epson capability test page (text sizes, barcode, QR, raster image)
|
||||
* `/labels` -- shared repository of saved ZPL labels / ESC-POS receipts (list, edit, preview, print, delete)
|
||||
* `/brother` -- list of Brother QL printers
|
||||
* `/brother/<printer>/*` -- proxied to that printer's brother_ql_web instance (UI and API alike)
|
||||
|
||||
== API usage (e.g. from Gitea Actions)
|
||||
|
||||
`POST /shipping/zpl` and `POST /epson/raw` are plain, unauthenticated form-POST endpoints -- no session/login needed, and printing without a `save_as`/`commit` param just prints (nothing gets kept in the label repository). That makes labelprint usable as a "dumb" print API for other automation: it doesn't need to know anything about the caller, and the caller doesn't need any labelprint-specific client, just an HTTP form POST.
|
||||
|
||||
Example: `randy/home-gitops` has a workflow (`.gitea/workflows/print-issues.yaml` + `scripts/print_issues.rb`) that fetches its own open issues via the Gitea API and prints a summary on the Epson receipt printer -- the Gitea-specific logic (fetching, formatting) lives entirely in that script, not here:
|
||||
|
||||
[source,ruby]
|
||||
----
|
||||
require 'net/http'
|
||||
|
||||
res = Net::HTTP.post_form(
|
||||
URI('http://rpi-printserver.hv.priesol.net/epson/raw'),
|
||||
'content' => "ESC E 0x01\nHello from CI\nESC E 0x00\nLF"
|
||||
)
|
||||
----
|
||||
|
||||
`content` uses labelprint's mnemonic ESC/POS notation: `ESC`/`GS`/`FS`/`LF`/`CR`/`HT`/`NUL` as command names, `0xNN` as a raw byte, anything else as literal text (CP852-encoded) -- see the `/epson` page for the full reference and worked examples (alignment, bold, barcodes). `POST /shipping/zpl` works the same way but with a `zpl` form field taking plain ZPL instead.
|
||||
|
||||
== Running
|
||||
|
||||
[source,shell]
|
||||
@@ -25,9 +50,9 @@ Requires `config.json` (see printer definitions inside) and a `.session_secret`
|
||||
|
||||
== Deployment
|
||||
|
||||
Runs as `sinatra.service` (systemd) on `rpi-printserver.local`, under RVM (`rvm default do bundle exec ruby app.rb` -- always uses whatever ruby is currently the RVM default, no manual bumping needed).
|
||||
Runs as `sinatra.service` (systemd) on `rpi-printserver.hv.priesol.net`, under RVM (`rvm default do bundle exec ruby app.rb` -- always uses whatever ruby is currently the RVM default, no manual bumping needed).
|
||||
|
||||
`.gitea/workflows/deploy.yaml` deploys on every push to `main`: SSH into `rpi-printserver.local`, `git pull`, `bundle install`, restart the service.
|
||||
`.gitea/workflows/deploy.yaml` deploys on every push to `main`: SSH into `rpi-printserver.hv.priesol.net`, `git pull`, `bundle install`, restart the service.
|
||||
|
||||
== Dependency updates
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ require_relative 'lib/label_repository'
|
||||
|
||||
set :protection, :except => :frame_options
|
||||
set :environment, :production
|
||||
set :bind, "::"
|
||||
set :port, 80
|
||||
# nginx reverse-proxies :80 -> here. Loopback-only and an unprivileged port
|
||||
# means no more cap_net_bind_service dance on every RVM ruby upgrade.
|
||||
set :bind, "127.0.0.1"
|
||||
set :port, 4567
|
||||
|
||||
if !File.exist?('config.json') || !File.readable?('config.json')
|
||||
raise RuntimeException, 'Could not read config.json'
|
||||
@@ -107,6 +109,56 @@ helpers do
|
||||
receipt.print!(PRINTER_DEVICE_EPSON)
|
||||
end
|
||||
|
||||
# Procedural 64x64 checkerboard (8px squares) -- no image library needed,
|
||||
# just to prove GS v 0 raster printing works end to end.
|
||||
def checkerboard_bitmap(size_px = 64, block_px = 8)
|
||||
width_bytes = size_px / 8
|
||||
bytes = "".b
|
||||
size_px.times do |y|
|
||||
row_block = y / block_px
|
||||
width_bytes.times do |xb|
|
||||
byte = 0
|
||||
8.times do |bit|
|
||||
x = xb * 8 + bit
|
||||
col_block = x / block_px
|
||||
byte |= (1 << (7 - bit)) if (row_block + col_block).even?
|
||||
end
|
||||
bytes << byte.chr
|
||||
end
|
||||
end
|
||||
bytes
|
||||
end
|
||||
|
||||
# Demonstrates most of what the printer can do: text sizes/styles, EAN-13,
|
||||
# QR code, and a raster image -- mirrors /test-print's role for the Datamax.
|
||||
def print_epson_test_page
|
||||
receipt = ReceiptBuilder.new
|
||||
receipt.align(:center).bold.size(:big).text("TM-T20II TEST\n").size(:normal).bold(false)
|
||||
receipt.align(:left).text(('-' * 48) + "\n")
|
||||
|
||||
receipt.section_separator('TEXT STYLES')
|
||||
receipt.text("Normal text\n")
|
||||
receipt.bold.text("Bold text\n").bold(false)
|
||||
receipt.underline.text("Underlined text\n").underline(false)
|
||||
receipt.size(:wide).text("Double width\n").size(:normal)
|
||||
receipt.size(:high).text("Double height\n").size(:normal)
|
||||
receipt.size(:big).text("Double both\n").size(:normal)
|
||||
|
||||
receipt.section_separator('BARCODE')
|
||||
receipt.align(:center).ean13('2012345678902')
|
||||
|
||||
receipt.section_separator('QR CODE')
|
||||
receipt.align(:center).qr('https://git.priesol.net/randy/home-gitops')
|
||||
|
||||
receipt.section_separator('IMAGE')
|
||||
receipt.align(:center).image(64, 64, checkerboard_bitmap)
|
||||
|
||||
receipt.align(:left).text(('-' * 48) + "\n")
|
||||
receipt.text("Printed: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
receipt.feed(3).cut
|
||||
receipt.print!(PRINTER_DEVICE_EPSON)
|
||||
end
|
||||
|
||||
ESCPOS_MNEMONICS = {
|
||||
'ESC' => "\x1B", 'GS' => "\x1D", 'FS' => "\x1C", 'DLE' => "\x10",
|
||||
'LF' => "\x0A", 'CR' => "\x0D", 'HT' => "\x09", 'NUL' => "\x00"
|
||||
@@ -117,17 +169,23 @@ helpers do
|
||||
# Hello # literal text
|
||||
# LF
|
||||
# GS k 0x03 12345678 0x00
|
||||
# Each `#...` trailing comment is stripped, then each line's
|
||||
# whitespace-separated tokens become either a known control mnemonic, a
|
||||
# raw `0xNN` byte, or literal text (CP852-encoded) -- consecutive literal
|
||||
# tokens are rejoined with a single space so plain prose still reads
|
||||
# correctly, while whitespace around control/byte tokens is dropped since
|
||||
# those are just for human readability, not printer output.
|
||||
# A trailing comment is only a comment when "#" is its own
|
||||
# whitespace-separated token -- not just stripped from the first "#"
|
||||
# anywhere in the line, which would mangle literal text like "#5 Fix
|
||||
# thing" (an issue reference, not a comment). Each remaining token
|
||||
# becomes a known control mnemonic, a raw `0xNN` byte, or literal text
|
||||
# (CP852-encoded) -- consecutive literal tokens are rejoined with a
|
||||
# single space so plain prose still reads correctly, while whitespace
|
||||
# around control/byte tokens is dropped since those are just for human
|
||||
# readability, not printer output.
|
||||
def parse_escpos_notation(text)
|
||||
bytes = "".b
|
||||
text.each_line do |line|
|
||||
prev_was_text = false
|
||||
line.sub(/#.*/, '').split.each do |token|
|
||||
tokens = line.split
|
||||
comment_at = tokens.index('#')
|
||||
tokens = tokens[0...comment_at] if comment_at
|
||||
tokens.each do |token|
|
||||
if ESCPOS_MNEMONICS.key?(token)
|
||||
bytes << ESCPOS_MNEMONICS[token]
|
||||
prev_was_text = false
|
||||
@@ -307,6 +365,16 @@ get '/epson' do
|
||||
erb :epson
|
||||
end
|
||||
|
||||
post '/epson/test-print' do
|
||||
begin
|
||||
print_epson_test_page
|
||||
flash 'Test page sent to Epson printer', 'success'
|
||||
rescue => e
|
||||
flash "Epson test print failed (#{e.message})", 'danger'
|
||||
end
|
||||
redirect '/epson'
|
||||
end
|
||||
|
||||
post '/epson/print' do
|
||||
begin
|
||||
print_receipt(title: params[:title].to_s, body: params[:body].to_s, qr: params[:qr].to_s)
|
||||
@@ -356,27 +424,27 @@ post '/epson/raw' do
|
||||
redirect '/epson'
|
||||
end
|
||||
|
||||
get '/labels' do
|
||||
get '/repository' do
|
||||
@labels = LABEL_REPO.all
|
||||
erb :labels
|
||||
erb :repository
|
||||
end
|
||||
|
||||
get '/labels/:id/edit' do
|
||||
get '/repository/:id/edit' do
|
||||
@label = LABEL_REPO.find(params[:id])
|
||||
halt 404, 'unknown label' unless @label
|
||||
|
||||
erb :label_edit
|
||||
end
|
||||
|
||||
post '/labels/:id' do
|
||||
post '/repository/:id' do
|
||||
label = LABEL_REPO.update(params[:id], name: params[:name].to_s, content: params[:content].to_s)
|
||||
halt 404, 'unknown label' unless label
|
||||
|
||||
flash "Saved '#{label['name']}'", 'success'
|
||||
redirect '/labels'
|
||||
redirect '/repository'
|
||||
end
|
||||
|
||||
get '/labels/:id/preview.png' do
|
||||
get '/repository/:id/preview.png' do
|
||||
label = LABEL_REPO.find(params[:id])
|
||||
halt 404 unless label && label['type'] == 'zpl'
|
||||
|
||||
@@ -387,7 +455,7 @@ get '/labels/:id/preview.png' do
|
||||
png
|
||||
end
|
||||
|
||||
post '/labels/:id/print' do
|
||||
post '/repository/:id/print' do
|
||||
label = LABEL_REPO.find(params[:id])
|
||||
halt 404, 'unknown label' unless label
|
||||
|
||||
@@ -402,13 +470,13 @@ post '/labels/:id/print' do
|
||||
rescue => e
|
||||
flash "Print failed (#{e.message})", 'danger'
|
||||
end
|
||||
redirect '/labels'
|
||||
redirect '/repository'
|
||||
end
|
||||
|
||||
post '/labels/:id/delete' do
|
||||
post '/repository/:id/delete' do
|
||||
LABEL_REPO.delete(params[:id])
|
||||
flash 'Label deleted', 'success'
|
||||
redirect '/labels'
|
||||
redirect '/repository'
|
||||
end
|
||||
|
||||
get '/brother' do
|
||||
|
||||
+36
-2
@@ -65,6 +65,28 @@ class ReceiptBuilder
|
||||
self
|
||||
end
|
||||
|
||||
# White-on-black reverse video (GS B n) -- available if a receipt ever
|
||||
# wants a real highlight, but plain "=" rules read better for routine
|
||||
# section breaks than a fully blacked-out line.
|
||||
def invert(enable = true)
|
||||
@buffer << (enable ? "\x1D\x42\x01" : "\x1D\x42\x00")
|
||||
self
|
||||
end
|
||||
|
||||
# A single "=" rule with an optional label centered in it, e.g.
|
||||
# section_separator("SECTION") -> "========= SECTION =========".
|
||||
def section_separator(label = '', width: 48)
|
||||
line = if label.empty?
|
||||
'=' * width
|
||||
else
|
||||
mid = " #{label} "
|
||||
pad = [width - mid.length, 0].max
|
||||
left = pad / 2
|
||||
('=' * left) + mid + ('=' * (pad - left))
|
||||
end
|
||||
feed(1).text("#{line}\n").feed(1)
|
||||
end
|
||||
|
||||
def size(mode)
|
||||
@buffer << case mode
|
||||
when :big then TXT_BIG
|
||||
@@ -87,12 +109,24 @@ class ReceiptBuilder
|
||||
self
|
||||
end
|
||||
|
||||
# Native ESC/POS EAN-13
|
||||
def ean13(code)
|
||||
# Native ESC/POS EAN-13. height is in dots (GS h) -- the printer's default
|
||||
# (~162 dots) prints noticeably tall; 50 is a shorter, still-scannable size.
|
||||
def ean13(code, height: 50)
|
||||
@buffer << "\x1D\x68" << height.chr # GS h n -- set barcode height
|
||||
@buffer << "\x1D\x6B\x02" << code.b << "\x00"
|
||||
self
|
||||
end
|
||||
|
||||
# Raster bit image (GS v 0) -- bytes must already be packed 1bpp,
|
||||
# row-major, (width_px/8 rounded up) bytes per row, MSB-first, 1=black.
|
||||
def image(width_px, height_px, bytes)
|
||||
width_bytes = (width_px + 7) / 8
|
||||
@buffer << "\x1D\x76\x30\x00"
|
||||
@buffer << [width_bytes % 256, width_bytes / 256, height_px % 256, height_px / 256].pack('C4')
|
||||
@buffer << bytes
|
||||
self
|
||||
end
|
||||
|
||||
def feed(lines = 3)
|
||||
@buffer << ("\n" * lines)
|
||||
self
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="sk">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="refresh" content="5">
|
||||
<title>Print server</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #212529; color: #adb5bd; }
|
||||
.mdi-printer-off { font-size: 4rem; color: #6c757d; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex align-items-center justify-content-center vh-100">
|
||||
<div class="text-center">
|
||||
<i class="mdi mdi-printer-off"></i>
|
||||
<h1 class="mt-3">Tlačiareň spí</h1>
|
||||
<p class="text-muted">Print server práve nabieha alebo sa reštartuje.<br>Stránka sa sama skúsi znova o chvíľu...</p>
|
||||
<div class="spinner-border text-secondary mt-2" role="status"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
# Installed at /etc/nginx/sites-available/labelprint on rpi-printserver
|
||||
# (symlinked into sites-enabled), not auto-deployed -- kept here just so a
|
||||
# reflash/rebuild isn't a guessing game. nginx owns :80 so the app itself
|
||||
# never needs cap_net_bind_service (which a plain RVM ruby patch upgrade
|
||||
# silently drops, since it's tied to the old binary's path).
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name rpi-printserver.hv.priesol.net;
|
||||
|
||||
client_max_body_size 20m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4567;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
error_page 502 503 504 /502.html;
|
||||
location = /502.html {
|
||||
root /etc/nginx/error_pages;
|
||||
internal;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Print server</a>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||
<div class="container-fluid d-flex align-items-center">
|
||||
<a class="navbar-brand" href="/"><i class="mdi mdi-printer"></i> 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>
|
||||
@@ -10,7 +10,7 @@
|
||||
<a class="nav-link <%= 'active' if request.path == '/' %>" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= 'active' if request.path.start_with?('/brother') %>" href="/brother">Brother</a>
|
||||
<a class="nav-link border-start <%= 'active' if request.path.start_with?('/brother') %>" href="/brother">Brother</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= 'active' if request.path == '/upload' %>" href="/upload">Shipping labels</a>
|
||||
@@ -19,7 +19,7 @@
|
||||
<a class="nav-link <%= 'active' if request.path == '/epson' %>" href="/epson">Receipts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= 'active' if request.path.start_with?('/labels') %>" href="/labels">Labels</a>
|
||||
<a class="nav-link border-start <%= 'active' if request.path.start_with?('/repository') %>" href="/repository">Labels</a>
|
||||
</li>
|
||||
</ul>
|
||||
<% if request.path.start_with?('/brother') %>
|
||||
|
||||
+5
-1
@@ -15,6 +15,10 @@
|
||||
<button class="btn btn-secondary" type="submit">Print</button>
|
||||
</form>
|
||||
|
||||
<form action="/epson/test-print" method="POST" class="mt-3">
|
||||
<button class="btn btn-outline-secondary" type="submit">Print test page (sizes, barcode, QR, image)</button>
|
||||
</form>
|
||||
|
||||
<hr class="mt-4">
|
||||
|
||||
<h5>Free text (ESC/POS mnemonic notation)</h5>
|
||||
@@ -47,4 +51,4 @@ GS V 0x00 # Full cut</code></pre>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="mt-4"><a href="/labels">See saved receipts →</a></p>
|
||||
<p class="mt-4"><a href="/repository">See saved receipts →</a></p>
|
||||
@@ -6,7 +6,7 @@
|
||||
<span class="badge bg-info text-dark">ESC/POS · Epson</span>
|
||||
<% end %>
|
||||
|
||||
<form action="/labels/<%= @label['id'] %>" method="POST" class="mt-3">
|
||||
<form action="/repository/<%= @label['id'] %>" method="POST" class="mt-3">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Name</label>
|
||||
<input name="name" class="form-control" type="text" id="name" value="<%= @label['name'] %>" />
|
||||
@@ -16,5 +16,5 @@
|
||||
<textarea name="content" class="form-control" id="content" rows="12" style="font-family: monospace;"><%= @label['content'] %></textarea>
|
||||
</div>
|
||||
<button class="btn btn-secondary" type="submit">Save</button>
|
||||
<a href="/labels" class="btn btn-outline-secondary">Cancel</a>
|
||||
<a href="/repository" class="btn btn-outline-secondary">Cancel</a>
|
||||
</form>
|
||||
+5
-4
@@ -5,12 +5,13 @@
|
||||
<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://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<%= erb :_navbar, layout: false %>
|
||||
<main class="container-fluid px-4">
|
||||
<div class="row">
|
||||
<% if @alert_success then %>
|
||||
<div class="alert alert-success"><%=@alert_success%></div>
|
||||
@@ -23,8 +24,8 @@
|
||||
<% 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>
|
||||
</main>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone-min.js"></script>
|
||||
<script>
|
||||
Dropzone.options.UploadForm = {
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
<strong class="ms-2"><%= label['name'] %></strong>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<form action="/labels/<%= label['id'] %>/print" method="POST">
|
||||
<form action="/repository/<%= label['id'] %>/print" method="POST">
|
||||
<button class="btn btn-sm btn-secondary" type="submit">Print</button>
|
||||
</form>
|
||||
<a href="/labels/<%= label['id'] %>/edit" class="btn btn-sm btn-outline-secondary">Edit</a>
|
||||
<form action="/labels/<%= label['id'] %>/delete" method="POST">
|
||||
<a href="/repository/<%= label['id'] %>/edit" class="btn btn-sm btn-outline-secondary">Edit</a>
|
||||
<form action="/repository/<%= label['id'] %>/delete" method="POST">
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<div class="mt-2">
|
||||
<% if label['type'] == 'zpl' %>
|
||||
<img src="/labels/<%= label['id'] %>/preview.png" alt="preview" style="max-height: 150px;" onerror="this.remove()" />
|
||||
<img src="/repository/<%= label['id'] %>/preview.png" alt="preview" style="max-height: 150px;" onerror="this.remove()" />
|
||||
<% else %>
|
||||
<pre class="bg-light p-2 mb-0" style="max-height: 150px; overflow: auto;"><%= label['content'] %></pre>
|
||||
<% end %>
|
||||
+1
-1
@@ -33,4 +33,4 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="mt-4"><a href="/labels">See saved labels →</a></p>
|
||||
<p class="mt-4"><a href="/repository">See saved labels →</a></p>
|
||||
Reference in new issue
Block a user