eksa-framework 0.1.0
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 +7 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +105 -0
- data/app/controllers/pages_controller.rb +50 -0
- data/app/models/pesan.rb +25 -0
- data/app/views/about.html.erb +36 -0
- data/app/views/docs.html.erb +120 -0
- data/app/views/edit.html.erb +50 -0
- data/app/views/index.html.erb +108 -0
- data/app/views/layout.html.erb +127 -0
- data/config.ru +15 -0
- data/db/eksa_app.db +0 -0
- data/db/setup.rb +13 -0
- data/exe/eksa-init +43 -0
- data/lib/eksa/controller.rb +43 -0
- data/lib/eksa/model.rb +25 -0
- data/lib/eksa/version.rb +3 -0
- data/lib/eksa.rb +43 -0
- data/public/css/style.css +111 -0
- data/public/img/favicon.ico +0 -0
- data/public/img/logo.png +0 -0
- metadata +105 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dd935a9e6ef4c2873496c96eac7b209180f6745789efc34c82dd1695bef46a9c
|
|
4
|
+
data.tar.gz: b737c65117b5a2263ad9d2fb4fae2f7ebf9c97ec0296bbe76b379d24bfbc2a3c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 40c3b4c98b336b078ff1cdaef039a4eb4d0827f904f2e158cb59ced2a8f345cb0a5be2c0b9bd36a9ae7be4531ff6fd2d7001a04378cb50f0ff105555664bea65
|
|
7
|
+
data.tar.gz: 90a0aaf449c9c8519112da50157fdd9d216f796fb0cd4ec38584debbf81e24099a2d4ec0cdd93c3a30e902cc6e9db3422ca75d49b73322ebda2d1af9fd27b11d
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 IshikawaUta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# ✨ Eksa Framework
|
|
2
|
+
|
|
3
|
+
[](https://www.ruby-lang.org/)
|
|
4
|
+
[](https://rack.github.io/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](#)
|
|
7
|
+
|
|
8
|
+
**Eksa Framework** adalah *micro-framework* MVC (Model-View-Controller) modern yang dibangun di atas Ruby dan Rack. Didesain untuk pengembang yang menginginkan kecepatan, kode yang bersih, dan tampilan antarmuka **Glassmorphism** yang elegan secara *out-of-the-box*.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 🚀 Fitur Unggulan
|
|
13
|
+
- 💎 **Modern Glassmorphism UI**: Tampilan transparan yang indah dengan Tailwind CSS & Lucide Icons.
|
|
14
|
+
- ⚡ **Rack 3 Ready**: Menggunakan standar terbaru dengan penanganan header modern.
|
|
15
|
+
- 🛠️ **CLI Generator**: Siapkan struktur project instan dengan perintah `eksa-init`.
|
|
16
|
+
- 💾 **Auto-Migration DB**: Database SQLite otomatis dibuat saat server pertama kali dijalankan.
|
|
17
|
+
- 🔔 **Flash Messages**: Notifikasi animasi elegan yang kompatibel dengan cookie Rack 3.
|
|
18
|
+
- 🛤️ **Simple Routing**: Sistem routing yang eksplisit dan mudah dikelola di `config.ru`.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🛠️ Instalasi Cepat
|
|
23
|
+
|
|
24
|
+
### 1. Install via Gem
|
|
25
|
+
Anda bisa menginstal framework ini langsung dengan `gem`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gem install eksa-framework
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Inisialisasi Project Baru
|
|
32
|
+
|
|
33
|
+
Masuk ke folder baru, lalu jalankan generator Eksa:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mkdir my-awesome-app
|
|
37
|
+
cd my-awesome-app
|
|
38
|
+
eksa-init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. Jalankan Server
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bundle install
|
|
45
|
+
rackup config.ru
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Buka browser dan akses `http://localhost:9292`.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 💻 Panduan Pengembangan
|
|
53
|
+
|
|
54
|
+
### 1. Struktur Folder
|
|
55
|
+
|
|
56
|
+
Eksa memisahkan data dari logika aplikasi dengan folder `db` di level root:
|
|
57
|
+
|
|
58
|
+
* `app/`: Logic & Views (Controllers, Models, Views).
|
|
59
|
+
* `db/`: File database SQLite `eksa_app.db` (Terpisah dari app).
|
|
60
|
+
* `lib/eksa/`: Core Engine (The magic happens here).
|
|
61
|
+
* `public/`: File statis (CSS, Images, JS).
|
|
62
|
+
|
|
63
|
+
### 2. Menambah Route & Controller
|
|
64
|
+
|
|
65
|
+
Daftarkan rute di `config.ru`:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
app.add_route "/profil", PagesController, :profil
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Buat method di `app/controllers/pages_controller.rb`:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
def profil
|
|
75
|
+
render :profil # Merujuk ke app/views/profil.html.erb
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3. Database & Model
|
|
80
|
+
|
|
81
|
+
Eksa akan otomatis membuat tabel saat Anda mendefinisikannya di `lib/eksa/model.rb`. Untuk menggunakan data di controller:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
@pesan = Pesan.all # Menggunakan model app/models/pesan.rb
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🎨 Kustomisasi Visual
|
|
90
|
+
|
|
91
|
+
Eksa menggunakan **Tailwind CSS** dan **Animate.css**. Anda dapat mengatur efek kaca pada class `.glass` di `app/views/layout.html.erb`:
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
.glass {
|
|
95
|
+
background: rgba(255, 255, 255, 0.05);
|
|
96
|
+
backdrop-filter: blur(16px);
|
|
97
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 📜 Lisensi
|
|
104
|
+
|
|
105
|
+
Proyek ini dilisensikan di bawah **MIT License**. Lihat file [LICENSE](https://www.google.com/search?q=LICENSE) untuk detail lebih lanjut.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require './app/models/pesan'
|
|
2
|
+
|
|
3
|
+
class PagesController < Eksa::Controller
|
|
4
|
+
def index
|
|
5
|
+
if request.post? && params['konten']
|
|
6
|
+
Pesan.buat(params['konten'], params['pengirim'])
|
|
7
|
+
return redirect_to "/", notice: "Pesan berhasil terkirim ke database!"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
if params['q'] && !params['q'].empty?
|
|
11
|
+
@semua_pesan = Pesan.cari_kata(params['q'])
|
|
12
|
+
@keyword = params['q']
|
|
13
|
+
else
|
|
14
|
+
@semua_pesan = Pesan.semua
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@nama = params['nama'] || "Developer"
|
|
18
|
+
render :index
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def about
|
|
22
|
+
render :about
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def docs
|
|
26
|
+
render :docs
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def edit
|
|
30
|
+
@id = params['id']
|
|
31
|
+
@pesan = Pesan.cari(@id)
|
|
32
|
+
|
|
33
|
+
return redirect_to "/", notice: "Pesan tidak ditemukan!" if @pesan.nil?
|
|
34
|
+
|
|
35
|
+
if request.post?
|
|
36
|
+
Pesan.update(@id, params['konten'], params['pengirim'])
|
|
37
|
+
return redirect_to "/", notice: "Perubahan pesan berhasil disimpan."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
render :edit
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def hapus_pesan
|
|
44
|
+
if params['id']
|
|
45
|
+
Pesan.hapus(params['id'])
|
|
46
|
+
return redirect_to "/", notice: "Pesan telah berhasil dihapus."
|
|
47
|
+
end
|
|
48
|
+
redirect_to "/"
|
|
49
|
+
end
|
|
50
|
+
end
|
data/app/models/pesan.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Pesan < Eksa::Model
|
|
2
|
+
def self.semua
|
|
3
|
+
db.execute("SELECT * FROM pesan ORDER BY id DESC")
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def self.buat(konten, pengirim)
|
|
7
|
+
db.execute("INSERT INTO pesan (konten, pengirim) VALUES (?, ?)", [konten.strip, pengirim.strip])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.cari(id)
|
|
11
|
+
db.execute("SELECT * FROM pesan WHERE id = ?", [id]).first
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.update(id, konten, pengirim)
|
|
15
|
+
db.execute("UPDATE pesan SET konten = ?, pengirim = ? WHERE id = ?", [konten.strip, pengirim.strip, id])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.hapus(id)
|
|
19
|
+
db.execute("DELETE FROM pesan WHERE id = ?", [id])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.cari_kata(keyword)
|
|
23
|
+
db.execute("SELECT * FROM pesan WHERE konten LIKE ? OR pengirim LIKE ? ORDER BY id DESC", ["%#{keyword}%", "%#{keyword}%"])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<div class="glass rounded-3xl p-10 text-white animate__animated animate__fadeIn">
|
|
2
|
+
<div class="flex items-center gap-4 mb-8">
|
|
3
|
+
<div class="p-4 bg-indigo-500 rounded-2xl shadow-xl">
|
|
4
|
+
<i data-lucide="rocket" class="w-8 h-8"></i>
|
|
5
|
+
</div>
|
|
6
|
+
<h1 class="text-4xl font-extrabold tracking-tight">Tentang <span class="text-indigo-300">Eksa</span></h1>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="space-y-6 text-white/80 leading-relaxed">
|
|
10
|
+
<p class="text-lg">
|
|
11
|
+
<strong>Eksa Framework</strong> adalah sebuah <strong>micro-framework</strong> Ruby yang dibangun dari nol untuk mendemonstrasikan kekuatan arsitektur MVC dan kesederhanaan Ruby.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-8">
|
|
15
|
+
<div class="bg-white/5 p-6 rounded-2xl border border-white/10">
|
|
16
|
+
<i data-lucide="cpu" class="w-6 h-6 text-indigo-300 mb-3"></i>
|
|
17
|
+
<h3 class="font-bold text-white mb-2">Core Ruby</h3>
|
|
18
|
+
<p class="text-sm">Dibangun di atas Rack tanpa menggunakan framework besar lainnya.</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="bg-white/5 p-6 rounded-2xl border border-white/10">
|
|
21
|
+
<i data-lucide="database" class="w-6 h-6 text-indigo-300 mb-3"></i>
|
|
22
|
+
<h3 class="font-bold text-white mb-2">SQLite Engine</h3>
|
|
23
|
+
<p class="text-sm">Penyimpanan data yang ringan, cepat, dan handal.</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="mt-12 pt-8 border-t border-white/10 flex justify-between items-center">
|
|
29
|
+
<a href="/" class="flex items-center gap-2 text-indigo-300 hover:text-white transition">
|
|
30
|
+
<i data-lucide="arrow-left" class="w-4 h-4"></i> Kembali ke Home
|
|
31
|
+
</a>
|
|
32
|
+
<span class="text-xs opacity-40 italic">Handcrafted with Ruby</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<script>lucide.createIcons();</script>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<div class="glass rounded-3xl p-8 md:p-12 animate__animated animate__fadeIn">
|
|
2
|
+
<div class="border-b border-white/10 pb-8 mb-8">
|
|
3
|
+
<div class="flex items-center gap-4 mb-4">
|
|
4
|
+
<div class="p-3 bg-indigo-500 rounded-2xl shadow-lg">
|
|
5
|
+
<i data-lucide="book-open" class="w-8 h-8"></i>
|
|
6
|
+
</div>
|
|
7
|
+
<h1 class="text-4xl font-extrabold tracking-tight">Dokumentasi <span class="text-indigo-300">Eksa</span></h1>
|
|
8
|
+
</div>
|
|
9
|
+
<p class="text-white/60 text-lg">Panduan profesional instalasi dan pengembangan aplikasi menggunakan Eksa Framework.</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
|
|
13
|
+
<div class="space-y-4">
|
|
14
|
+
<h3 class="font-bold text-indigo-300 uppercase text-xs tracking-widest">Memulai</h3>
|
|
15
|
+
<ul class="space-y-2 text-sm">
|
|
16
|
+
<li><a href="#instalasi" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="download" class="w-3 h-3"></i> Instalasi Gem</a></li>
|
|
17
|
+
<li><a href="#struktur" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="folder" class="w-3 h-3"></i> Struktur Project</a></li>
|
|
18
|
+
</ul>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="space-y-4">
|
|
21
|
+
<h3 class="font-bold text-indigo-300 uppercase text-xs tracking-widest">Pengembangan</h3>
|
|
22
|
+
<ul class="space-y-2 text-sm">
|
|
23
|
+
<li><a href="#routing" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="git-branch" class="w-3 h-3"></i> Routing System</a></li>
|
|
24
|
+
<li><a href="#build" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="package" class="w-3 h-3"></i> Build Framework</a></li>
|
|
25
|
+
</ul>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="space-y-4">
|
|
28
|
+
<h3 class="font-bold text-indigo-300 uppercase text-xs tracking-widest">Fitur Core</h3>
|
|
29
|
+
<ul class="space-y-2 text-sm">
|
|
30
|
+
<li><a href="#database" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="database" class="w-3 h-3"></i> Auto-Migration</a></li>
|
|
31
|
+
<li><a href="#flash" class="hover:text-indigo-300 transition flex items-center gap-2"><i data-lucide="bell" class="w-3 h-3"></i> Flash UI</a></li>
|
|
32
|
+
</ul>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<section id="instalasi" class="mb-12 scroll-mt-24">
|
|
37
|
+
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
38
|
+
<i data-lucide="terminal" class="w-6 h-6 text-indigo-400"></i> Instalasi Cepat
|
|
39
|
+
</h2>
|
|
40
|
+
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10 space-y-3">
|
|
41
|
+
<div>
|
|
42
|
+
<p class="text-white/30"># 1. Install gem secara lokal</p>
|
|
43
|
+
<p class="text-indigo-300">gem install ./eksa-framework-0.1.0.gem</p>
|
|
44
|
+
</div>
|
|
45
|
+
<div>
|
|
46
|
+
<p class="text-white/30"># 2. Inisialisasi project baru</p>
|
|
47
|
+
<p class="text-indigo-300">eksa-init</p>
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
<p class="text-white/30"># 3. Jalankan server</p>
|
|
51
|
+
<p class="text-indigo-300">rackup config.ru</p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<section id="routing" class="mb-12 scroll-mt-24">
|
|
57
|
+
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
58
|
+
<i data-lucide="git-branch" class="w-6 h-6 text-indigo-400"></i> Routing System
|
|
59
|
+
</h2>
|
|
60
|
+
<p class="text-white/60 mb-4 text-sm">Definisikan rute aplikasi Anda di dalam file <code class="text-indigo-200">config.ru</code>. Eksa mendukung pemetaan URL ke Controller dan Action secara eksplisit.</p>
|
|
61
|
+
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
62
|
+
<p class="text-white/30"># app.add_route(path, controller_class, action_symbol)</p>
|
|
63
|
+
<p class="text-indigo-300 italic">app.add_route "/", PagesController, :index</p>
|
|
64
|
+
<p class="text-indigo-300 italic">app.add_route "/about", PagesController, :about</p>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<section id="database" class="mb-12 scroll-mt-24">
|
|
69
|
+
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
70
|
+
<i data-lucide="database" class="w-6 h-6 text-indigo-400"></i> Auto-Migration
|
|
71
|
+
</h2>
|
|
72
|
+
<p class="text-white/60 mb-4 text-sm">Eksa secara otomatis membuat file database dan tabel saat aplikasi pertama kali dijalankan melalui <code class="text-indigo-200">Eksa::Model</code>.</p>
|
|
73
|
+
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
74
|
+
<p class="text-indigo-300">def self.setup_initial_schema</p>
|
|
75
|
+
<p class="text-white/70 ml-4">@db.execute "CREATE TABLE IF NOT EXISTS pesan (...)"</p>
|
|
76
|
+
<p class="text-indigo-300">end</p>
|
|
77
|
+
</div>
|
|
78
|
+
<p class="text-xs text-white/40 mt-3 italic">* Database tersimpan secara persisten di folder /db/eksa_app.db</p>
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<section id="flash" class="mb-12 scroll-mt-24">
|
|
82
|
+
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
83
|
+
<i data-lucide="bell" class="w-6 h-6 text-indigo-400"></i> Flash UI Notification
|
|
84
|
+
</h2>
|
|
85
|
+
<p class="text-white/60 mb-4 text-sm">Kirim feedback instan ke user menggunakan fitur redirect dengan notice.</p>
|
|
86
|
+
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
87
|
+
<p class="text-white/30"># Di dalam Controller</p>
|
|
88
|
+
<p class="text-indigo-300">redirect_to "/", notice: "Data berhasil disimpan!"</p>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="mt-4 p-4 bg-emerald-500/10 border border-emerald-500/20 rounded-xl flex items-center gap-3">
|
|
91
|
+
<i data-lucide="info" class="w-5 h-5 text-emerald-400"></i>
|
|
92
|
+
<p class="text-xs text-emerald-200/80">Sistem Flash Eksa menggunakan cookie sementara yang otomatis dihapus setelah notifikasi muncul (Standar Rack 3).</p>
|
|
93
|
+
</div>
|
|
94
|
+
</section>
|
|
95
|
+
|
|
96
|
+
<section id="build" class="mb-12 scroll-mt-24">
|
|
97
|
+
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
98
|
+
<i data-lucide="package-check" class="w-6 h-6 text-indigo-400"></i> Build ke RubyGems
|
|
99
|
+
</h2>
|
|
100
|
+
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
101
|
+
<p class="text-indigo-300">gem build eksa-framework.gemspec</p>
|
|
102
|
+
<p class="text-indigo-300 mt-2">gem install ./eksa-framework-0.1.0.gem</p>
|
|
103
|
+
</div>
|
|
104
|
+
</section>
|
|
105
|
+
|
|
106
|
+
<div class="mt-12 pt-8 border-t border-white/10 text-center">
|
|
107
|
+
<div class="flex justify-center gap-4 mb-4">
|
|
108
|
+
<span class="bg-indigo-500/20 text-indigo-300 text-[10px] px-3 py-1 rounded-full border border-indigo-500/30 uppercase tracking-tighter">Rack 3.0 Compatible</span>
|
|
109
|
+
<span class="bg-emerald-500/20 text-emerald-300 text-[10px] px-3 py-1 rounded-full border border-emerald-500/30 uppercase tracking-tighter">SQLite3 Auto-Ready</span>
|
|
110
|
+
</div>
|
|
111
|
+
<p class="text-white/40 text-sm italic">Eksa Framework v<%= Eksa::VERSION %> Alpha Documentation</p>
|
|
112
|
+
<a href="/" class="inline-block mt-6 text-indigo-300 hover:text-white transition font-bold flex items-center justify-center gap-2">
|
|
113
|
+
<i data-lucide="arrow-left" class="w-4 h-4"></i> Kembali ke Beranda
|
|
114
|
+
</a>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<script>
|
|
119
|
+
lucide.createIcons();
|
|
120
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<div class="glass rounded-3xl p-8 max-w-xl mx-auto animate__animated animate__zoomIn">
|
|
2
|
+
<div class="flex items-center gap-4 mb-6">
|
|
3
|
+
<div class="p-3 bg-indigo-500/20 rounded-2xl border border-indigo-500/30 text-indigo-300">
|
|
4
|
+
<i data-lucide="edit-3" class="w-6 h-6"></i>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<span class="text-xs font-bold uppercase tracking-widest text-indigo-300 opacity-80">Mode Edit</span>
|
|
8
|
+
<h1 class="text-3xl font-bold tracking-tight text-white">Revisi Pesan <span class="text-indigo-300">#<%= @id %></span></h1>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<p class="text-white/60 mb-8">
|
|
13
|
+
Lakukan perubahan pada konten atau pengirim. Perubahan akan langsung diperbarui di database SQLite.
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<form action="/edit?id=<%= @id %>" method="post" class="space-y-6">
|
|
17
|
+
<div class="space-y-2">
|
|
18
|
+
<label class="text-sm font-semibold text-white/80 ml-1 flex items-center gap-2">
|
|
19
|
+
<i data-lucide="user" class="w-4 h-4 text-indigo-300"></i> Nama Pengirim
|
|
20
|
+
</label>
|
|
21
|
+
<input type="text" name="pengirim" value="<%= @pesan[2] %>" required
|
|
22
|
+
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition text-white placeholder-white/20">
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="space-y-2">
|
|
26
|
+
<label class="text-sm font-semibold text-white/80 ml-1 flex items-center gap-2">
|
|
27
|
+
<i data-lucide="message-circle" class="w-4 h-4 text-indigo-300"></i> Isi Pesan
|
|
28
|
+
</label>
|
|
29
|
+
<textarea name="konten" required
|
|
30
|
+
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition text-white placeholder-white/20 h-40 resize-none"><%= @pesan[1] %></textarea>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="flex flex-col sm:flex-row gap-4 pt-4">
|
|
34
|
+
<button type="submit"
|
|
35
|
+
class="flex-[2] bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-6 rounded-xl transition duration-300 flex items-center justify-center gap-2 shadow-lg shadow-indigo-900/20">
|
|
36
|
+
<i data-lucide="save" class="w-5 h-5"></i> Simpan Perubahan
|
|
37
|
+
</button>
|
|
38
|
+
|
|
39
|
+
<a href="/"
|
|
40
|
+
class="flex-[1] bg-white/5 hover:bg-white/10 text-white text-center font-bold py-3 px-6 rounded-xl border border-white/10 transition flex items-center justify-center gap-2">
|
|
41
|
+
<i data-lucide="arrow-left" class="w-4 h-4 text-white/60"></i> Batal
|
|
42
|
+
</a>
|
|
43
|
+
</div>
|
|
44
|
+
</form>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<script>
|
|
48
|
+
// Inisialisasi ikon untuk halaman ini
|
|
49
|
+
lucide.createIcons();
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<div class="glass rounded-3xl p-8 animate__animated animate__fadeIn">
|
|
2
|
+
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
|
|
3
|
+
<div>
|
|
4
|
+
<span class="inline-block bg-indigo-500/20 text-indigo-200 text-xs font-bold px-3 py-1 rounded-full mb-3 border border-indigo-500/30">
|
|
5
|
+
v0.1.0 Alpha
|
|
6
|
+
</span>
|
|
7
|
+
<h1 class="text-4xl font-extrabold tracking-tight">
|
|
8
|
+
Halo, <span class="text-indigo-300"><%= @nama %></span>!
|
|
9
|
+
</h1>
|
|
10
|
+
<p class="text-white/60 mt-2">
|
|
11
|
+
Selamat datang di dunia pengembangan web yang bersih dan cepat dengan <strong>Eksa Framework</strong>.
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<form action="/" method="get" class="flex w-full md:w-auto">
|
|
16
|
+
<div class="relative w-full">
|
|
17
|
+
<input type="text" name="q" placeholder="Cari pesan..." value="<%= @keyword %>"
|
|
18
|
+
class="w-full md:w-48 bg-white/5 border border-white/10 rounded-xl py-2 pl-4 pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:w-64 transition-all duration-300 placeholder-white/30 text-sm">
|
|
19
|
+
<button type="submit" class="absolute right-2 top-1.5 p-1 text-white/40 hover:text-white transition">
|
|
20
|
+
<i data-lucide="search" class="w-4 h-4"></i>
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
</form>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="grid grid-cols-1 lg:grid-cols-5 gap-8">
|
|
27
|
+
|
|
28
|
+
<div class="lg:col-span-2 space-y-6">
|
|
29
|
+
<div class="bg-white/5 border border-white/10 rounded-2xl p-6">
|
|
30
|
+
<h2 class="text-lg font-bold mb-4 flex items-center gap-2">
|
|
31
|
+
<i data-lucide="pen-tool" class="w-5 h-5 text-indigo-300"></i> Kirim Pesan
|
|
32
|
+
</h2>
|
|
33
|
+
<form action="/" method="post" class="space-y-4">
|
|
34
|
+
<div>
|
|
35
|
+
<input type="text" name="pengirim" placeholder="Nama Anda" required
|
|
36
|
+
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition placeholder-white/20">
|
|
37
|
+
</div>
|
|
38
|
+
<div>
|
|
39
|
+
<textarea name="konten" placeholder="Tulis pesan kreatifmu di sini..." required
|
|
40
|
+
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition placeholder-white/20 h-32 resize-none"></textarea>
|
|
41
|
+
</div>
|
|
42
|
+
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 rounded-xl transition duration-300 flex items-center justify-center gap-2 shadow-lg shadow-indigo-900/20">
|
|
43
|
+
<i data-lucide="send" class="w-4 h-4"></i> Simpan ke SQLite
|
|
44
|
+
</button>
|
|
45
|
+
</form>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="lg:col-span-3">
|
|
50
|
+
<div class="flex items-center justify-between mb-4">
|
|
51
|
+
<h2 class="text-lg font-bold flex items-center gap-2">
|
|
52
|
+
<i data-lucide="message-square" class="w-5 h-5 text-indigo-300"></i> Pesan Terbaru
|
|
53
|
+
</h2>
|
|
54
|
+
<% if @keyword && !@keyword.empty? %>
|
|
55
|
+
<a href="/" class="text-xs bg-white/10 hover:bg-white/20 px-3 py-1 rounded-lg transition flex items-center gap-1">
|
|
56
|
+
<i data-lucide="x" class="w-3 h-3"></i> Bersihkan Filter
|
|
57
|
+
</a>
|
|
58
|
+
<% end %>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<% if @keyword && !@keyword.empty? %>
|
|
62
|
+
<p class="text-sm text-white/40 mb-4 italic">Menampilkan hasil pencarian untuk "<%= @keyword %>"</p>
|
|
63
|
+
<% end %>
|
|
64
|
+
|
|
65
|
+
<div class="space-y-4 overflow-y-auto max-h-[500px] pr-2 custom-scrollbar">
|
|
66
|
+
<% if @semua_pesan.any? %>
|
|
67
|
+
<% @semua_pesan.each do |p| %>
|
|
68
|
+
<div class="bg-white/5 border border-white/10 p-5 rounded-2xl flex justify-between items-start group hover:bg-white/10 transition duration-300 animate__animated animate__fadeInUp">
|
|
69
|
+
<div class="max-w-[70%]">
|
|
70
|
+
<div class="flex items-center gap-2 mb-1">
|
|
71
|
+
<div class="w-2 h-2 bg-indigo-400 rounded-full"></div>
|
|
72
|
+
<strong class="text-indigo-200"><%= p[2] %></strong>
|
|
73
|
+
</div>
|
|
74
|
+
<p class="text-white/70 text-sm leading-relaxed"><%= p[1] %></p>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
78
|
+
<a href="/edit?id=<%= p[0] %>" title="Edit"
|
|
79
|
+
class="p-2 bg-white/10 hover:bg-indigo-500 rounded-lg transition">
|
|
80
|
+
<i data-lucide="edit-3" class="w-4 h-4 text-white"></i>
|
|
81
|
+
</a>
|
|
82
|
+
<a href="/hapus?id=<%= p[0] %>"
|
|
83
|
+
onclick="return confirm('Yakin ingin menghapus pesan dari <%= p[2] %>?')"
|
|
84
|
+
title="Hapus"
|
|
85
|
+
class="p-2 bg-white/10 hover:bg-red-500 rounded-lg transition">
|
|
86
|
+
<i data-lucide="trash-2" class="w-4 h-4 text-white"></i>
|
|
87
|
+
</a>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<% end %>
|
|
91
|
+
<% else %>
|
|
92
|
+
<div class="flex flex-col items-center justify-center py-20 bg-white/5 rounded-2xl border border-dashed border-white/10">
|
|
93
|
+
<i data-lucide="ghost" class="w-12 h-12 text-white/10 mb-2"></i>
|
|
94
|
+
<p class="text-white/30 italic text-sm">
|
|
95
|
+
<%= @keyword ? "Tidak ada pesan yang cocok." : "Database masih kosong." %>
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
<% end %>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<script>
|
|
106
|
+
// Refresh icons for new content
|
|
107
|
+
lucide.createIcons();
|
|
108
|
+
</script>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="id">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Eksa Framework | Modern Ruby Development</title>
|
|
7
|
+
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
11
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
12
|
+
<link rel="icon" href="/img/logo.png" type="image/png">
|
|
13
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
|
|
14
|
+
<script src="https://unpkg.com/lucide@latest"></script>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
body {
|
|
18
|
+
font-family: 'Inter', sans-serif;
|
|
19
|
+
background: radial-gradient(circle at top left, #4f46e5, #764ba2, #2d3748);
|
|
20
|
+
background-attachment: fixed;
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.glass {
|
|
27
|
+
background: rgba(255, 255, 255, 0.1);
|
|
28
|
+
backdrop-filter: blur(16px) saturate(180%);
|
|
29
|
+
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
30
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.nav-blur {
|
|
34
|
+
background: rgba(255, 255, 255, 0.05);
|
|
35
|
+
backdrop-filter: blur(10px);
|
|
36
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.nav-link {
|
|
40
|
+
position: relative;
|
|
41
|
+
transition: all 0.3s ease;
|
|
42
|
+
}
|
|
43
|
+
.nav-link::after {
|
|
44
|
+
content: '';
|
|
45
|
+
position: absolute;
|
|
46
|
+
width: 0;
|
|
47
|
+
height: 2px;
|
|
48
|
+
bottom: -4px;
|
|
49
|
+
left: 0;
|
|
50
|
+
background-color: #a5b4fc;
|
|
51
|
+
transition: width 0.3s ease;
|
|
52
|
+
}
|
|
53
|
+
.nav-link:hover::after {
|
|
54
|
+
width: 100%;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Custom Scrollbar untuk list pesan */
|
|
58
|
+
.custom-scrollbar::-webkit-scrollbar {
|
|
59
|
+
width: 6px;
|
|
60
|
+
}
|
|
61
|
+
.custom-scrollbar::-webkit-scrollbar-track {
|
|
62
|
+
background: rgba(255, 255, 255, 0.05);
|
|
63
|
+
}
|
|
64
|
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
65
|
+
background: rgba(255, 255, 255, 0.2);
|
|
66
|
+
border-radius: 10px;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
69
|
+
</head>
|
|
70
|
+
<body class="text-white antialiased">
|
|
71
|
+
|
|
72
|
+
<% if @flash && @flash[:notice] %>
|
|
73
|
+
<div id="flash-msg" class="fixed top-24 left-1/2 -translate-x-1/2 z-[60] animate__animated animate__fadeInDown">
|
|
74
|
+
<div class="flex items-center gap-3 bg-emerald-500/90 backdrop-blur-md text-white px-6 py-3 rounded-2xl shadow-2xl border border-emerald-400/50 shadow-emerald-900/40">
|
|
75
|
+
<i data-lucide="check-circle" class="w-5 h-5"></i>
|
|
76
|
+
<span class="font-bold text-sm tracking-wide"><%= @flash[:notice] %></span>
|
|
77
|
+
<button onclick="closeFlash()" class="ml-4 opacity-50 hover:opacity-100 transition">
|
|
78
|
+
<i data-lucide="x" class="w-4 h-4"></i>
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<script>
|
|
83
|
+
function closeFlash() {
|
|
84
|
+
const msg = document.getElementById('flash-msg');
|
|
85
|
+
if(msg) {
|
|
86
|
+
msg.classList.replace('animate__fadeInDown', 'animate__fadeOutUp');
|
|
87
|
+
setTimeout(() => msg.remove(), 800);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Otomatis tutup setelah 4 detik
|
|
91
|
+
setTimeout(closeFlash, 4000);
|
|
92
|
+
</script>
|
|
93
|
+
<% end %>
|
|
94
|
+
|
|
95
|
+
<nav class="nav-blur sticky top-0 z-50 px-6 py-4 flex items-center justify-between animate__animated animate__fadeInDown">
|
|
96
|
+
<div onclick="window.location.href='/'" class="flex items-center gap-3 group cursor-pointer">
|
|
97
|
+
<div class="p-2 bg-indigo-500 rounded-xl shadow-lg shadow-indigo-500/50 group-hover:rotate-12 transition-transform">
|
|
98
|
+
<i data-lucide="layers" class="w-6 h-6"></i>
|
|
99
|
+
</div>
|
|
100
|
+
<span class="text-xl font-extrabold tracking-tight">Eksa<span class="text-indigo-300">Framework</span></span>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<ul class="hidden md:flex items-center gap-8 font-medium text-sm">
|
|
104
|
+
<li><a href="/" class="nav-link flex items-center gap-2"><i data-lucide="home" class="w-4 h-4"></i> Home</a></li>
|
|
105
|
+
<li><a href="/about" class="nav-link flex items-center gap-2"><i data-lucide="info" class="w-4 h-4"></i> About</a></li>
|
|
106
|
+
<li><a href="/docs" class="nav-link flex items-center gap-2"><i data-lucide="book-open" class="w-4 h-4"></i> Docs</a></li>
|
|
107
|
+
</ul>
|
|
108
|
+
|
|
109
|
+
<div class="flex items-center gap-4">
|
|
110
|
+
<a href="https://github.com/IshikawaUta" target="_blank" class="p-2 hover:bg-white/10 rounded-full transition"><i data-lucide="github" class="w-5 h-5"></i></a>
|
|
111
|
+
</div>
|
|
112
|
+
</nav>
|
|
113
|
+
|
|
114
|
+
<main class="max-w-6xl mx-auto py-12 px-6 flex-grow animate__animated animate__fadeInUp">
|
|
115
|
+
<%= @content %>
|
|
116
|
+
</main>
|
|
117
|
+
|
|
118
|
+
<footer class="py-8 text-center text-white/40 text-sm border-t border-white/5">
|
|
119
|
+
<p>© <%= Time.now.year %> <span class="font-bold text-white/60">Eksa Framework</span>. Crafted for speed.</p>
|
|
120
|
+
</footer>
|
|
121
|
+
|
|
122
|
+
<script>
|
|
123
|
+
// Inisialisasi ikon Lucide
|
|
124
|
+
lucide.createIcons();
|
|
125
|
+
</script>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
data/config.ru
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require './lib/eksa'
|
|
2
|
+
require './app/controllers/pages_controller'
|
|
3
|
+
|
|
4
|
+
use Rack::Static, urls: ["/css", "/img"], root: "public"
|
|
5
|
+
use Rack::ShowExceptions
|
|
6
|
+
|
|
7
|
+
app = Eksa::Application.new
|
|
8
|
+
|
|
9
|
+
app.add_route "/", PagesController, :index
|
|
10
|
+
app.add_route "/hapus", PagesController, :hapus_pesan
|
|
11
|
+
app.add_route "/edit", PagesController, :edit
|
|
12
|
+
app.add_route "/about", PagesController, :about
|
|
13
|
+
app.add_route "/docs", PagesController, :docs
|
|
14
|
+
|
|
15
|
+
run app
|
data/db/eksa_app.db
ADDED
|
Binary file
|
data/db/setup.rb
ADDED
data/exe/eksa-init
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
gem_root = File.expand_path("../..", __FILE__)
|
|
7
|
+
target_dir = Dir.pwd
|
|
8
|
+
|
|
9
|
+
puts "\n✨ Eksa Framework Generator ✨"
|
|
10
|
+
puts "-----------------------------"
|
|
11
|
+
puts "🚀 Menginisialisasi project di: #{target_dir}"
|
|
12
|
+
|
|
13
|
+
items_to_init = ['app', 'lib', 'public', 'db', 'config.ru', 'Gemfile', 'README.md']
|
|
14
|
+
|
|
15
|
+
items_to_init.each do |item|
|
|
16
|
+
source = File.join(gem_root, item)
|
|
17
|
+
destination = File.join(target_dir, item)
|
|
18
|
+
|
|
19
|
+
if File.exist?(source)
|
|
20
|
+
if File.directory?(source)
|
|
21
|
+
FileUtils.cp_r(source, target_dir)
|
|
22
|
+
puts " [OK] Folder '#{item}' berhasil disalin."
|
|
23
|
+
else
|
|
24
|
+
FileUtils.cp(source, destination)
|
|
25
|
+
puts " [OK] File '#{item}' berhasil disalin."
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
if item == 'db'
|
|
29
|
+
FileUtils.mkdir_p(destination)
|
|
30
|
+
puts " [OK] Folder 'db' baru berhasil dibuat."
|
|
31
|
+
else
|
|
32
|
+
puts " [!] Warning: Sumber '#{item}' tidak ditemukan."
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts "\n✅ Inisialisasi Selesai!"
|
|
38
|
+
puts "-----------------------------"
|
|
39
|
+
puts "Instruksi selanjutnya:"
|
|
40
|
+
puts "1. Jalankan: bundle install"
|
|
41
|
+
puts "2. Jalankan server: rackup config.ru"
|
|
42
|
+
puts "3. Buka browser: http://localhost:9292"
|
|
43
|
+
puts "-----------------------------\n"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
|
|
3
|
+
module Eksa
|
|
4
|
+
class Controller
|
|
5
|
+
attr_reader :request
|
|
6
|
+
attr_accessor :status, :redirect_url, :flash
|
|
7
|
+
|
|
8
|
+
def initialize(request)
|
|
9
|
+
@request = request
|
|
10
|
+
@status = 200
|
|
11
|
+
@flash = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def params
|
|
15
|
+
@request.params
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def redirect_to(url, notice: nil)
|
|
19
|
+
@status = 302
|
|
20
|
+
@redirect_url = url
|
|
21
|
+
@flash[:notice] = notice if notice
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render(template_name, variables = {})
|
|
26
|
+
variables.each { |k, v| instance_variable_set("@#{k}", v) }
|
|
27
|
+
|
|
28
|
+
content_path = File.expand_path("./app/views/#{template_name}.html.erb")
|
|
29
|
+
layout_path = File.expand_path("./app/views/layout.html.erb")
|
|
30
|
+
|
|
31
|
+
if File.exist?(content_path)
|
|
32
|
+
@content = ERB.new(File.read(content_path)).result(binding)
|
|
33
|
+
if File.exist?(layout_path)
|
|
34
|
+
ERB.new(File.read(layout_path)).result(binding)
|
|
35
|
+
else
|
|
36
|
+
@content
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
"Error: View '#{template_name}' tidak ditemukan."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/eksa/model.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'sqlite3'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module Eksa
|
|
5
|
+
class Model
|
|
6
|
+
def self.db
|
|
7
|
+
db_dir = File.expand_path("../../db", __dir__)
|
|
8
|
+
db_path = File.join(db_dir, "eksa_app.db")
|
|
9
|
+
FileUtils.mkdir_p(db_dir) unless Dir.exist?(db_dir)
|
|
10
|
+
@db ||= SQLite3::Database.new(db_path)
|
|
11
|
+
setup_initial_schema
|
|
12
|
+
@db
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.setup_initial_schema
|
|
16
|
+
@db.execute <<-SQL
|
|
17
|
+
CREATE TABLE IF NOT EXISTS pesan (
|
|
18
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
19
|
+
konten TEXT,
|
|
20
|
+
pengirim TEXT
|
|
21
|
+
);
|
|
22
|
+
SQL
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/eksa/version.rb
ADDED
data/lib/eksa.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'rack'
|
|
2
|
+
require_relative 'eksa/version'
|
|
3
|
+
require_relative 'eksa/controller'
|
|
4
|
+
require_relative 'eksa/model'
|
|
5
|
+
|
|
6
|
+
module Eksa
|
|
7
|
+
class Application
|
|
8
|
+
def initialize
|
|
9
|
+
@routes = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_route(path, controller_class, action)
|
|
13
|
+
@routes[path] = { controller: controller_class, action: action }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(env)
|
|
17
|
+
request = Rack::Request.new(env)
|
|
18
|
+
flash_message = request.cookies['eksa_flash']
|
|
19
|
+
route = @routes[request.path_info]
|
|
20
|
+
|
|
21
|
+
if route
|
|
22
|
+
controller_instance = route[:controller].new(request)
|
|
23
|
+
controller_instance.flash[:notice] = flash_message if flash_message
|
|
24
|
+
|
|
25
|
+
response_body = controller_instance.send(route[:action])
|
|
26
|
+
|
|
27
|
+
response = Rack::Response.new
|
|
28
|
+
|
|
29
|
+
if controller_instance.status == 302
|
|
30
|
+
response.redirect(controller_instance.redirect_url, 302)
|
|
31
|
+
else
|
|
32
|
+
response.write(response_body)
|
|
33
|
+
response['content-type'] = 'text/html'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
response.delete_cookie('eksa_flash') if flash_message
|
|
37
|
+
response.finish
|
|
38
|
+
else
|
|
39
|
+
[404, { 'content-type' => 'text/html' }, ["<h1>404 - Not Found</h1>"]]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/* public/css/style.css */
|
|
2
|
+
|
|
3
|
+
* {
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
background-color: #f8fafc; /* Abu-abu sangat muda yang bersih */
|
|
11
|
+
font-family: 'Inter', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
12
|
+
color: #1e293b;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.main-content {
|
|
19
|
+
flex: 1;
|
|
20
|
+
display: flex;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
align-items: center;
|
|
23
|
+
padding: 20px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
ul.nav-links {
|
|
28
|
+
list-style: none;
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: 20px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.modern-nav {
|
|
34
|
+
background: #ffffff;
|
|
35
|
+
padding: 1rem 10%;
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
align-items: center;
|
|
39
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.brand {
|
|
43
|
+
font-weight: 800;
|
|
44
|
+
font-size: 1.4rem;
|
|
45
|
+
color: #4f46e5; /* Indigo modern */
|
|
46
|
+
letter-spacing: -1px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.modern-card {
|
|
50
|
+
background: white;
|
|
51
|
+
padding: 3.5rem 2.5rem;
|
|
52
|
+
border-radius: 24px;
|
|
53
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.05);
|
|
54
|
+
text-align: center;
|
|
55
|
+
max-width: 500px;
|
|
56
|
+
width: 100%;
|
|
57
|
+
border: 1px solid #f1f5f9;
|
|
58
|
+
position: relative;
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.modern-card::before {
|
|
63
|
+
content: "";
|
|
64
|
+
position: absolute;
|
|
65
|
+
top: 0;
|
|
66
|
+
left: 0;
|
|
67
|
+
right: 0;
|
|
68
|
+
height: 6px;
|
|
69
|
+
background: linear-gradient(90deg, #4f46e5, #ec4899); /* Gradien Indigo ke Pink */
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
h1 {
|
|
73
|
+
font-size: 2.2rem;
|
|
74
|
+
font-weight: 800;
|
|
75
|
+
margin-bottom: 1rem;
|
|
76
|
+
color: #0f172a;
|
|
77
|
+
letter-spacing: -0.025em;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
p {
|
|
81
|
+
color: #64748b;
|
|
82
|
+
line-height: 1.6;
|
|
83
|
+
margin-bottom: 1.5rem;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.badge {
|
|
87
|
+
display: inline-block;
|
|
88
|
+
padding: 4px 12px;
|
|
89
|
+
background: #eef2ff;
|
|
90
|
+
color: #4f46e5;
|
|
91
|
+
border-radius: 99px;
|
|
92
|
+
font-size: 0.75rem;
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
margin-bottom: 1rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
code {
|
|
98
|
+
background: #f1f5f9;
|
|
99
|
+
padding: 3px 6px;
|
|
100
|
+
border-radius: 6px;
|
|
101
|
+
color: #e11d48;
|
|
102
|
+
font-family: 'Fira Code', monospace;
|
|
103
|
+
font-size: 0.9em;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.modern-footer {
|
|
107
|
+
padding: 2rem;
|
|
108
|
+
text-align: center;
|
|
109
|
+
font-size: 0.85rem;
|
|
110
|
+
color: #94a3b8;
|
|
111
|
+
}
|
|
Binary file
|
data/public/img/logo.png
ADDED
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: eksa-framework
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- IshikawaUta
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: sqlite3
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.4'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.4'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: puma
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '6.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '6.0'
|
|
54
|
+
description: Framework MVC ringan dengan tema modern, sistem routing, dan auto-database
|
|
55
|
+
SQLite.
|
|
56
|
+
email:
|
|
57
|
+
- komikers09@gmail.com
|
|
58
|
+
executables:
|
|
59
|
+
- eksa-init
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- Gemfile
|
|
64
|
+
- LICENSE
|
|
65
|
+
- README.md
|
|
66
|
+
- app/controllers/pages_controller.rb
|
|
67
|
+
- app/models/pesan.rb
|
|
68
|
+
- app/views/about.html.erb
|
|
69
|
+
- app/views/docs.html.erb
|
|
70
|
+
- app/views/edit.html.erb
|
|
71
|
+
- app/views/index.html.erb
|
|
72
|
+
- app/views/layout.html.erb
|
|
73
|
+
- config.ru
|
|
74
|
+
- db/eksa_app.db
|
|
75
|
+
- db/setup.rb
|
|
76
|
+
- exe/eksa-init
|
|
77
|
+
- lib/eksa.rb
|
|
78
|
+
- lib/eksa/controller.rb
|
|
79
|
+
- lib/eksa/model.rb
|
|
80
|
+
- lib/eksa/version.rb
|
|
81
|
+
- public/css/style.css
|
|
82
|
+
- public/img/favicon.ico
|
|
83
|
+
- public/img/logo.png
|
|
84
|
+
homepage: https://github.com/IshikawaUta/eksa-framework
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata: {}
|
|
88
|
+
rdoc_options: []
|
|
89
|
+
require_paths:
|
|
90
|
+
- lib
|
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: 3.0.0
|
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0'
|
|
101
|
+
requirements: []
|
|
102
|
+
rubygems_version: 3.6.7
|
|
103
|
+
specification_version: 4
|
|
104
|
+
summary: Modern Glassmorphism Ruby MVC Framework.
|
|
105
|
+
test_files: []
|