tina4ruby 3.10.32 → 3.10.38
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/lib/tina4/ai.rb +237 -211
- data/lib/tina4/cli.rb +5 -13
- data/lib/tina4/dev_admin.rb +281 -2
- data/lib/tina4/frond.rb +15 -3
- data/lib/tina4/metrics.rb +673 -0
- data/lib/tina4/rack_app.rb +57 -2
- data/lib/tina4/request.rb +40 -2
- data/lib/tina4/response.rb +7 -2
- data/lib/tina4/router.rb +5 -1
- data/lib/tina4/version.rb +1 -1
- metadata +8 -4
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -616,16 +616,71 @@ module Tina4
|
|
|
616
616
|
|
|
617
617
|
toolbar = <<~HTML.strip
|
|
618
618
|
<div id="tina4-dev-toolbar" style="position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px;">
|
|
619
|
-
<span style="color:#d32f2f;font-weight:bold;">Tina4 v#{version}</span>
|
|
619
|
+
<span id="tina4-ver-btn" style="color:#d32f2f;font-weight:bold;cursor:pointer;text-decoration:underline dotted;" onclick="tina4VersionModal()" title="Click to check for updates">Tina4 v#{version}</span>
|
|
620
|
+
<div id="tina4-ver-modal" style="display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #d32f2f;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,0.5);font-family:monospace;font-size:13px;color:#cdd6f4;">
|
|
621
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
622
|
+
<strong style="color:#89b4fa;">Version Info</strong>
|
|
623
|
+
<span onclick="document.getElementById('tina4-ver-modal').style.display='none'" style="cursor:pointer;color:#888;">×</span>
|
|
624
|
+
</div>
|
|
625
|
+
<div id="tina4-ver-body" style="line-height:1.8;">
|
|
626
|
+
<div>Current: <strong style="color:#a6e3a1;">v#{version}</strong></div>
|
|
627
|
+
<div id="tina4-ver-latest" style="color:#888;">Checking for updates...</div>
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
620
630
|
<span style="color:#4caf50;">#{method}</span>
|
|
621
631
|
<span>#{path}</span>
|
|
622
632
|
<span style="color:#666;">→ #{matched_pattern}</span>
|
|
623
633
|
<span style="color:#ffeb3b;">req:#{request_id}</span>
|
|
624
634
|
<span style="color:#90caf9;">#{route_count} routes</span>
|
|
625
635
|
<span style="color:#888;">Ruby #{RUBY_VERSION}</span>
|
|
626
|
-
<a href="#" onclick="(function(e){e.preventDefault();var p=document.getElementById('tina4-dev-panel');if(p){p.style.display=p.style.display==='none'?'block':'none';return;}var c=document.createElement('div');c.id='tina4-dev-panel';c.style.cssText='position:fixed;
|
|
636
|
+
<a href="#" onclick="(function(e){e.preventDefault();var p=document.getElementById('tina4-dev-panel');if(p){p.style.display=p.style.display==='none'?'block':'none';return;}var c=document.createElement('div');c.id='tina4-dev-panel';c.style.cssText='position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';var f=document.createElement('iframe');f.src='/__dev';f.style.cssText='width:100%;height:100%;border:1px solid #CC342D;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';c.appendChild(f);document.body.appendChild(c);})(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard ↗</a>
|
|
627
637
|
<span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">✕</span>
|
|
628
638
|
</div>
|
|
639
|
+
<script>
|
|
640
|
+
function tina4VersionModal(){
|
|
641
|
+
var m=document.getElementById('tina4-ver-modal');
|
|
642
|
+
if(m.style.display==='block'){m.style.display='none';return;}
|
|
643
|
+
m.style.display='block';
|
|
644
|
+
var el=document.getElementById('tina4-ver-latest');
|
|
645
|
+
el.innerHTML='Checking for updates...';
|
|
646
|
+
el.style.color='#888';
|
|
647
|
+
fetch('/__dev/api/version-check')
|
|
648
|
+
.then(function(r){return r.json()})
|
|
649
|
+
.then(function(d){
|
|
650
|
+
var latest=d.latest;
|
|
651
|
+
var current=d.current;
|
|
652
|
+
if(latest===current){
|
|
653
|
+
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
654
|
+
el.style.color='#a6e3a1';
|
|
655
|
+
}else{
|
|
656
|
+
var cParts=current.split('.').map(Number);
|
|
657
|
+
var lParts=latest.split('.').map(Number);
|
|
658
|
+
var isNewer=false;
|
|
659
|
+
for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
|
|
660
|
+
var c=cParts[i]||0,l=lParts[i]||0;
|
|
661
|
+
if(l>c){isNewer=true;break;}
|
|
662
|
+
if(l<c)break;
|
|
663
|
+
}
|
|
664
|
+
if(isNewer){
|
|
665
|
+
var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
|
|
666
|
+
el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
|
|
667
|
+
if(breaking){
|
|
668
|
+
el.innerHTML+='<div style="color:#f38ba8;margin-top:6px;">⚠ Major/minor version change — check the <a href="https://github.com/tina4stack/tina4-ruby/releases" target="_blank" style="color:#89b4fa;">changelog</a> for breaking changes before upgrading.</div>';
|
|
669
|
+
}else{
|
|
670
|
+
el.innerHTML+='<div style="color:#f9e2af;margin-top:6px;">Patch update available. Run: <code style="background:#313244;padding:2px 6px;border-radius:3px;">gem install tina4ruby</code></div>';
|
|
671
|
+
}
|
|
672
|
+
}else{
|
|
673
|
+
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
674
|
+
el.style.color='#a6e3a1';
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
})
|
|
678
|
+
.catch(function(){
|
|
679
|
+
el.innerHTML='Could not check for updates (offline?)';
|
|
680
|
+
el.style.color='#f38ba8';
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
</script>
|
|
629
684
|
HTML
|
|
630
685
|
|
|
631
686
|
if body.include?("</body>")
|
data/lib/tina4/request.rb
CHANGED
|
@@ -3,6 +3,38 @@ require "uri"
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
5
|
module Tina4
|
|
6
|
+
# A Hash subclass that supports indifferent access (both string and symbol keys).
|
|
7
|
+
# Used by Request#params so that params[:id] and params["id"] both work.
|
|
8
|
+
class IndifferentHash < Hash
|
|
9
|
+
def [](key)
|
|
10
|
+
super(convert_key(key))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def []=(key, value)
|
|
14
|
+
super(convert_key(key), value)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fetch(key, *args, &block)
|
|
18
|
+
super(convert_key(key), *args, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def key?(key)
|
|
22
|
+
super(convert_key(key))
|
|
23
|
+
end
|
|
24
|
+
alias has_key? key?
|
|
25
|
+
alias include? key?
|
|
26
|
+
|
|
27
|
+
def delete(key, &block)
|
|
28
|
+
super(convert_key(key), &block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def convert_key(key)
|
|
34
|
+
key.is_a?(Symbol) ? key.to_s : key
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
6
38
|
class Request
|
|
7
39
|
attr_reader :env, :method, :path, :query_string, :content_type,
|
|
8
40
|
:path_params, :ip
|
|
@@ -88,10 +120,16 @@ module Tina4
|
|
|
88
120
|
end
|
|
89
121
|
|
|
90
122
|
# Merged params: query + body + path_params (path_params highest priority)
|
|
123
|
+
# Supports both string and symbol key access (indifferent access).
|
|
91
124
|
def params
|
|
92
125
|
@params ||= build_params
|
|
93
126
|
end
|
|
94
127
|
|
|
128
|
+
# Look up a param by symbol or string key (indifferent access shortcut).
|
|
129
|
+
def param(key)
|
|
130
|
+
params[key.to_s] || params[key.to_sym]
|
|
131
|
+
end
|
|
132
|
+
|
|
95
133
|
def [](key)
|
|
96
134
|
params[key.to_s] || params[key.to_sym] || @path_params[key.to_sym]
|
|
97
135
|
end
|
|
@@ -168,10 +206,10 @@ module Tina4
|
|
|
168
206
|
end
|
|
169
207
|
|
|
170
208
|
def build_params
|
|
171
|
-
p =
|
|
209
|
+
p = IndifferentHash.new
|
|
172
210
|
|
|
173
211
|
# Query string params
|
|
174
|
-
query.each { |k, v| p[k] = v }
|
|
212
|
+
query.each { |k, v| p[k.to_s] = v }
|
|
175
213
|
|
|
176
214
|
# Body params
|
|
177
215
|
body_parsed.each { |k, v| p[k.to_s] = v }
|
data/lib/tina4/response.rb
CHANGED
|
@@ -103,10 +103,15 @@ module Tina4
|
|
|
103
103
|
self
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
def render(template_path, data = {}, status: 200)
|
|
106
|
+
def render(template_path, data = {}, status: 200, template_dir: nil)
|
|
107
107
|
@status_code = status
|
|
108
108
|
@headers["content-type"] = HTML_CONTENT_TYPE
|
|
109
|
-
|
|
109
|
+
if template_dir
|
|
110
|
+
frond = Tina4::Frond.new(template_dir: template_dir)
|
|
111
|
+
@body = frond.render(template_path, data)
|
|
112
|
+
else
|
|
113
|
+
@body = Tina4::Template.render(template_path, data)
|
|
114
|
+
end
|
|
110
115
|
self
|
|
111
116
|
end
|
|
112
117
|
|
data/lib/tina4/router.rb
CHANGED
|
@@ -89,10 +89,14 @@ module Tina4
|
|
|
89
89
|
parts = path.split("/").reject(&:empty?)
|
|
90
90
|
regex_parts = parts.map do |part|
|
|
91
91
|
if part =~ /\A\*(\w+)\z/
|
|
92
|
-
#
|
|
92
|
+
# Named catch-all splat parameter: *path captures everything after
|
|
93
93
|
name = Regexp.last_match(1)
|
|
94
94
|
@param_names << { name: name.to_sym, type: "path" }
|
|
95
95
|
'(.+)'
|
|
96
|
+
elsif part == "*"
|
|
97
|
+
# Bare catch-all wildcard: captures everything after (unnamed)
|
|
98
|
+
@param_names << { name: :splat, type: "path" }
|
|
99
|
+
'(.+)'
|
|
96
100
|
elsif part =~ /\A\{(\w+)(?::(\w+))?\}\z/
|
|
97
101
|
# Tina4/Python-style brace params: {id} or {id:int}
|
|
98
102
|
# This is the ONLY supported param syntax, matching Python exactly.
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.10.
|
|
4
|
+
version: 3.10.38
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: rack
|
|
@@ -170,7 +171,7 @@ dependencies:
|
|
|
170
171
|
- - "~>"
|
|
171
172
|
- !ruby/object:Gem::Version
|
|
172
173
|
version: '1.8'
|
|
173
|
-
type: :
|
|
174
|
+
type: :runtime
|
|
174
175
|
prerelease: false
|
|
175
176
|
version_requirements: !ruby/object:Gem::Requirement
|
|
176
177
|
requirements:
|
|
@@ -323,6 +324,7 @@ files:
|
|
|
323
324
|
- lib/tina4/log.rb
|
|
324
325
|
- lib/tina4/mcp.rb
|
|
325
326
|
- lib/tina4/messenger.rb
|
|
327
|
+
- lib/tina4/metrics.rb
|
|
326
328
|
- lib/tina4/middleware.rb
|
|
327
329
|
- lib/tina4/migration.rb
|
|
328
330
|
- lib/tina4/orm.rb
|
|
@@ -401,6 +403,7 @@ licenses:
|
|
|
401
403
|
- MIT
|
|
402
404
|
metadata:
|
|
403
405
|
homepage_uri: https://tina4.com
|
|
406
|
+
post_install_message:
|
|
404
407
|
rdoc_options: []
|
|
405
408
|
require_paths:
|
|
406
409
|
- lib
|
|
@@ -415,7 +418,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
415
418
|
- !ruby/object:Gem::Version
|
|
416
419
|
version: '0'
|
|
417
420
|
requirements: []
|
|
418
|
-
rubygems_version: 4.
|
|
421
|
+
rubygems_version: 3.4.19
|
|
422
|
+
signing_key:
|
|
419
423
|
specification_version: 4
|
|
420
424
|
summary: Simple. Fast. Human. This is not a framework.
|
|
421
425
|
test_files: []
|