From f5470aeb6ce35b1d8024fc896e303e3f5b863be7 Mon Sep 17 00:00:00 2001 From: MOHAMMED SAAD MOHAMMED ALOHAYDIB <77123695+s200077761@users.noreply.github.com> Date: Wed, 23 Apr 2025 21:57:15 +0300 Subject: [PATCH 1/6] Create static.yml iris scan app Signed-off-by: MOHAMMED SAAD MOHAMMED ALOHAYDIB <77123695+s200077761@users.noreply.github.com> --- .github/workflows/static.yml | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/static.yml diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 000000000..f2c9e97c9 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 8d3a3b6661d91f8d452142b49b17118b33239185 Mon Sep 17 00:00:00 2001 From: MOHAMMED SAAD MOHAMMED ALOHAYDIB <77123695+s200077761@users.noreply.github.com> Date: Thu, 15 May 2025 06:00:05 +0300 Subject: [PATCH 2/6] Create docker-image.yml Signed-off-by: MOHAMMED SAAD MOHAMMED ALOHAYDIB <77123695+s200077761@users.noreply.github.com> --- .github/workflows/docker-image.yml | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 000000000..f7ffbdd9e --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,48 @@ +name: Docker Image CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) +name: Deploy New Design + +on: + push: + branches: + - main # Trigger deployment on push to the main branch + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install Dependencies + run: npm install + + - name: Build Project + run: npm run build + + - name: Deploy to Hosting Service + env: + API_KEY: ${{ secrets.DEPLOY_KEY }} + run: npm run deploy From 64c926e0f30fbd48ba3da9cf50e936bb6265c8a0 Mon Sep 17 00:00:00 2001 From: MOHAMMED SAAD MOHAMMED ALOHAYDIB <77123695+s200077761@users.noreply.github.com> Date: Tue, 3 Mar 2026 07:11:55 +0300 Subject: [PATCH 3/6] Add GitHub Actions workflow for Jekyll site deployment This workflow automates the building and deployment of a Jekyll site to GitHub Pages, including steps for checking out the code, building the site, and deploying it. Signed-off-by: MOHAMMED SAAD MOHAMMED ALOHAYDIB <77123695+s200077761@users.noreply.github.com> --- .github/workflows/jekyll-gh-pages.yml | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/jekyll-gh-pages.yml diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml new file mode 100644 index 000000000..e31d81c58 --- /dev/null +++ b/.github/workflows/jekyll-gh-pages.yml @@ -0,0 +1,51 @@ +# Sample workflow for building and deploying a Jekyll site to GitHub Pages +name: Deploy Jekyll with GitHub Pages dependencies preinstalled + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./ + destination: ./_site + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 7bdd455f7507a64f3b0b5b50ae1e0eec7152ba72 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Mar 2026 05:56:37 +0000 Subject: [PATCH 4/6] Add comprehensive code review for Iris Web Framework https://claude.ai/code/session_01KpAo1Z18ekFZxaakKDitYF --- CODE_REVIEW.md | 135 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 CODE_REVIEW.md diff --git a/CODE_REVIEW.md b/CODE_REVIEW.md new file mode 100644 index 000000000..71c1e0e92 --- /dev/null +++ b/CODE_REVIEW.md @@ -0,0 +1,135 @@ +# مراجعة كود مشروع Iris Web Framework + +## نظرة عامة على المشروع + +هذا المشروع هو **Iris Web Framework** - إطار عمل ويب مكتوب بلغة Go (الإصدار 12.2.11). +يتكون من ~282 ملف Go موزعة على حوالي 18 حزمة رئيسية. + +--- + +## هيكل المشروع + +``` +iris/ +├── iris.go # النقطة الرئيسية - Application struct +├── configuration.go # إعدادات الإطار (YAML/TOML/JSON) +├── aliases.go # Type aliases للتسهيل على المستخدمين +├── context/ # السياق الأساسي للطلبات (context.Context) +├── core/ # النواة: router, host, netutil, memstore +├── hero/ # Dependency Injection +├── middleware/ # Middlewares مدمجة (cors, recover, requestid...) +├── mvc/ # نمط MVC +├── sessions/ # إدارة الجلسات +├── view/ # محركات القوالب (HTML, Pug, Jet, Django...) +├── i18n/ # التعريب والترجمة +├── auth/ # المصادقة +├── cache/ # التخزين المؤقت +├── websocket/ # WebSocket +├── macro/ # Route macros و path parameters +├── versioning/ # API versioning +├── httptest/ # أدوات الاختبار +├── apps/ # Multi-app support +├── x/ # حزم تجريبية +├── _examples/ # أمثلة شاملة +└── _benchmarks/ # اختبارات الأداء +``` + +--- + +## النقاط الإيجابية + +### 1. تصميم معماري ناضج +- فصل واضح بين الطبقات (Context, Router, Host, View) +- نمط الـ **Configurator Pattern** يوفر مرونة ممتازة في الإعدادات +- دعم متعدد لمحركات القوالب عبر interface موحد (`view.Engine`) + +### 2. API سهل الاستخدام +- `aliases.go` يوفر اختصارات ذكية تقلل من الـ imports المطلوبة +- `New()` و `Default()` يقدمان مستويين من التهيئة (بسيط ومتقدم) +- Method chaining مريح: `app.Configure(...).SetName(...)` + +### 3. إدارة جيدة للموارد +- استخدام `sync.Pool` للـ Context عبر `context.Pool` - ممتاز للأداء +- Mutex protection مناسب على `Application` struct (`mu sync.RWMutex`) +- دعم graceful shutdown مدمج مع signal handling + +### 4. ميزات شاملة +- دعم TLS و AutoTLS (Let's Encrypt) مدمج +- Tunneling (ngrok) مدمج +- Minification للاستجابات (CSS, HTML, JS, JSON, XML, SVG) +- Compression مدمج +- نظام I18n متكامل + +### 5. تغطية اختبارات +- وجود اختبارات في حزمة `hero/` مع ملفات `*_test.go` +- حزمة `httptest/` مخصصة لتسهيل الاختبارات + +--- + +## الملاحظات والمشاكل المكتشفة + +### 1. أخطاء إملائية في الكود (Typos) + +| الموقع | الخطأ | الصحيح | +|--------|-------|--------| +| `iris.go:88` | `builded` | `built` | +| `iris.go:101` | `envrinoment` (في التعليق) | `environment` | +| `iris.go:329` | `existss` (في التعليق) | `exists` | +| `iris.go:389` | `hoods` (في التعليق) | `hood` | +| `aliases.go:377` | `clopy` (في التعليق) | `copy` | +| `aliases.go:489` | `instaed` (في التعليق) | `instead` | +| `aliases.go:665` | `recude` (في التعليق) | `reduce` | +| `iris.go:692` | `builded = true` | `built = true` | + +### 2. استخدام `interface{}` بدل Generics +- `go.mod` يحدد `go 1.24` لكن الكود لا يزال يستخدم `interface{}` بكثرة بدل `any` (الذي هو alias لـ `interface{}` منذ Go 1.18) +- أمثلة: `iris.go:131`, `iris.go:342`, `iris.go:424` +- الـ README يذكر العمل على إصدار يعتمد Generics لكنه لم ينعكس بالكامل + +### 3. Global State و Package-level Variables +- `context.GetDomain` (في `iris.go:547`) هو متغير global يُعدّل في runtime - هذا خطير في حالة تشغيل عدة تطبيقات Iris في نفس العملية +- `context.WriteJSON`, `context.WriteJSONP`, إلخ (في `aliases.go`) - نفس المشكلة +- `context.SetCookieKVExpiration` - حالة مشتركة عالمية + +### 4. كود مُعلّق (Dead Code) +- `iris.go:151-167`: كتلة كود معلقة كبيرة تتعلق بـ access log (`/* #2046 ... */`) +- `iris.go:631-638`: دالة `OnShutdown` معلقة بالكامل +- `iris.go:346-355`: كود reflect معلق داخل `Validate()` +- `iris.go:797`: تعليق غير مكتمل (`// if end := time.Since(start)...`) + +### 5. معالجة أخطاء يمكن تحسينها +- `iris.go:342-365` (`Validate`): إذا كان `Validator` هو `nil`، يرجع `nil` بصمت بدون أي validation. قد يكون من الأفضل إرجاع خطأ أو على الأقل تسجيل تحذير +- `iris.go:1219`: `nolint:errcheck` يتجاهل خطأ الكتابة - يجب التعامل معه + +### 6. حقل `builded` بدل `built` +- `iris.go:87-88`: الحقل `builded` ليس فقط خطأ إملائي، بل أيضاً لا يوجد حماية thread-safe عليه (لا يستخدم atomic أو mutex عند قراءته في `Build()`) + +### 7. تعقيد الـ `aliases.go` +- الملف يحتوي على ~880 سطر معظمه type aliases وconstant aliases +- هذا يجعل من الصعب معرفة الأنواع الحقيقية ومصادرها +- يضيف طبقة indirection قد تربك المطورين الجدد + +### 8. شجرة اعتماديات ثقيلة +- `go.mod` يحتوي على ~50+ اعتماد مباشر و ~40+ اعتماد غير مباشر +- اعتماديات كبيرة مثل `badger/v4`, `bbolt`, `redis`, `protobuf` قد لا يحتاجها كل مستخدم +- يُفضل فصلها كحزم اختيارية (plugins) + +### 9. `Default()` يفتح CORS للجميع +- `iris.go:179-183`: `cors.AllowAnyOrigin` - هذا قد يكون خطير أمنياً إذا استُخدم في الإنتاج دون تعديل +- يجب أن يكون هناك تحذير أوضح في التوثيق + +--- + +## التقييم العام + +| المعيار | التقييم | +|---------|---------| +| هيكل المشروع | جيد جداً | +| جودة الكود | جيد (مع بعض الملاحظات) | +| التوثيق | جيد (أمثلة كثيرة) | +| الأمان | يحتاج انتباه (CORS, global state) | +| الاختبارات | متوسط (ليست شاملة لكل الحزم) | +| الأداء | ممتاز (sync.Pool, minification, compression) | +| سهولة الاستخدام | ممتاز | + +**الخلاصة**: مشروع ناضج وشامل مع API مصمم بعناية. أبرز ما يحتاج تحسين هو: تنظيف الكود المعلق، إصلاح الأخطاء الإملائية، تقليل الـ global state، والانتقال الكامل لـ Generics بما يتوافق مع Go 1.24. From 4fd0399779155fba8405511ad55de80e73b11e01 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Mar 2026 06:07:34 +0000 Subject: [PATCH 5/6] Fix typos, remove dead code, and modernize Go idioms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename field `builded` to `built` across iris.go and configuration.go - Fix typos: envrinoment→environment, existss→exists, hoods→hood, clopy→copy, instaed→instead, recude→reduce, wihch→which - Remove commented-out dead code (access log #2046, OnShutdown, reflect validation, timing code) - Replace `interface{}` with `any` (Go 1.18+ idiomatic alias) - Handle errcheck properly in tunneling log write https://claude.ai/code/session_01KpAo1Z18ekFZxaakKDitYF --- aliases.go | 18 +++++++------- configuration.go | 2 +- iris.go | 65 ++++++++++-------------------------------------- 3 files changed, 23 insertions(+), 62 deletions(-) diff --git a/aliases.go b/aliases.go index bfb6e46ee..747af6b81 100644 --- a/aliases.go +++ b/aliases.go @@ -374,7 +374,7 @@ var ( // To skip HTTP Server logging for this type of warning: // app.Listen/Run(..., iris.WithoutServerError(iris.ErrURLQuerySemicolon)). AllowQuerySemicolons = func(ctx Context) { - // clopy of net/http.AllowQuerySemicolons. + // copy of net/http.AllowQuerySemicolons. r := ctx.Request() if s := r.URL.RawQuery; strings.Contains(s, ";") { r2 := new(http.Request) @@ -486,7 +486,7 @@ var ( // StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client. // It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration. // - // If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions. + // If "cacheDur" <=0 then it returns the `NoCache` middleware instead to disable the caching between browser's "back" and "forward" actions. // // Usage: `app.Use(iris.StaticCache(24 * time.Hour))` or `app.Use(iris.StaticCache(-1))`. // A middleware, which is a simple Handler can be called inside another handler as well, example: @@ -662,7 +662,7 @@ const ( // HTTP status codes as registered with IANA. // See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. -// Raw Copy from the future(tip) net/http std package in order to recude the import path of "net/http" for the users. +// Raw Copy from the future(tip) net/http std package in order to reduce the import path of "net/http" for the users. const ( StatusContinue = http.StatusContinue // RFC 7231, 6.2.1 StatusSwitchingProtocols = http.StatusSwitchingProtocols // RFC 7231, 6.2.2 @@ -835,13 +835,13 @@ func (cp *ContextPatches) SetCookieKVExpiration(patch time.Duration) { // ResolveHTTPFS modifies the default way to resolve a filesystem by any type of value. // It affects the Application's API Builder's `HandleDir` method. -func (cp *ContextPatches) ResolveHTTPFS(patchFunc func(fsOrDir interface{}) http.FileSystem) { +func (cp *ContextPatches) ResolveHTTPFS(patchFunc func(fsOrDir any) http.FileSystem) { context.ResolveHTTPFS = patchFunc } // ResolveHTTPFS modifies the default way to resolve a filesystem by any type of value. // It affects the view engine's filesystem resolver. -func (cp *ContextPatches) ResolveFS(patchFunc func(fsOrDir interface{}) fs.FS) { +func (cp *ContextPatches) ResolveFS(patchFunc func(fsOrDir any) fs.FS) { context.ResolveFS = patchFunc } @@ -849,17 +849,17 @@ func (cp *ContextPatches) ResolveFS(patchFunc func(fsOrDir interface{}) fs.FS) { type ContextWriterPatches struct{} // JSON sets a custom function which runs and overrides the default behavior of the `Context#JSON` method. -func (cwp *ContextWriterPatches) JSON(patchFunc func(ctx Context, v interface{}, options *JSON) error) { +func (cwp *ContextWriterPatches) JSON(patchFunc func(ctx Context, v any, options *JSON) error) { context.WriteJSON = patchFunc } // JSONP sets a custom function which runs and overrides the default behavior of the `Context#JSONP` method. -func (cwp *ContextWriterPatches) JSONP(patchFunc func(ctx Context, v interface{}, options *JSONP) error) { +func (cwp *ContextWriterPatches) JSONP(patchFunc func(ctx Context, v any, options *JSONP) error) { context.WriteJSONP = patchFunc } // XML sets a custom function which runs and overrides the default behavior of the `Context#XML` method. -func (cwp *ContextWriterPatches) XML(patchFunc func(ctx Context, v interface{}, options *XML) error) { +func (cwp *ContextWriterPatches) XML(patchFunc func(ctx Context, v any, options *XML) error) { context.WriteXML = patchFunc } @@ -869,7 +869,7 @@ func (cwp *ContextWriterPatches) Markdown(patchFunc func(ctx Context, v []byte, } // YAML sets a custom function which runs and overrides the default behavior of the `Context#YAML` method. -func (cwp *ContextWriterPatches) YAML(patchFunc func(ctx Context, v interface{}, indentSpace int) error) { +func (cwp *ContextWriterPatches) YAML(patchFunc func(ctx Context, v any, indentSpace int) error) { context.WriteYAML = patchFunc } diff --git a/configuration.go b/configuration.go index 848354d93..8a9f51dd0 100644 --- a/configuration.go +++ b/configuration.go @@ -588,7 +588,7 @@ func WithSitemap(startURL string) Configurator { ctx.ContentType(context.ContentXMLHeaderValue) ctx.Write(contentCopy) // nolint:errcheck } - if app.builded { + if app.built { routes := app.CreateRoutes([]string{MethodGet, MethodHead, MethodOptions}, s.Path, handler) for _, r := range routes { diff --git a/iris.go b/iris.go index fa0ece782..abc8b401c 100644 --- a/iris.go +++ b/iris.go @@ -84,7 +84,7 @@ type Application struct { // view engine view *view.View // used for build - builded bool + built bool defaultMode bool // OnBuild is a single function which // is fired on the first `Build` method call. @@ -98,7 +98,7 @@ type Application struct { mu sync.RWMutex // name is the application name and the log prefix for // that Application instance's Logger. See `SetName` and `String`. - // Defaults to IRIS_APP_NAME envrinoment variable otherwise empty. + // Defaults to IRIS_APP_NAME environment variable otherwise empty. name string // Hosts contains a list of all servers (Host Supervisors) that this app is running on. // @@ -128,7 +128,7 @@ func New() *Application { logger := newLogger(app) app.logger = logger app.APIBuilder = router.NewAPIBuilder(logger) - app.ContextPool = context.New(func() interface{} { + app.ContextPool = context.New(func() any { return context.NewContext(app) }) @@ -148,24 +148,6 @@ func Default() *Application { app.logger.SetLevel("debug") app.logger.Debugf(`Log level set to "debug"`) - /* #2046. - // Register the accesslog middleware. - logFile, err := os.OpenFile("./access.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600) - if err == nil { - // Close the file on shutdown. - app.ConfigureHost(func(su *Supervisor) { - su.RegisterOnShutdown(func() { - logFile.Close() - }) - }) - - ac := accesslog.New(logFile) - ac.AddOutput(app.logger.Printer) - app.UseRouter(ac.Handler) - app.logger.Debugf("Using <%s> to log requests", logFile.Name()) - } - */ - // Register the requestid middleware // before recover so current Context.GetID() contains the info on panic logs. app.UseRouter(requestid.New()) @@ -326,7 +308,7 @@ func (app *Application) Logger() *golog.Logger { // IsDebug reports whether the application is running // under debug/development mode. // It's just a shortcut of Logger().Level >= golog.DebugLevel. -// The same method existss as Context.IsDebug() too. +// The same method exists as Context.IsDebug() too. func (app *Application) IsDebug() bool { return app.logger.Level >= golog.DebugLevel } @@ -339,21 +321,11 @@ func (app *Application) I18nReadOnly() context.I18nReadOnly { // Validate validates a value and returns nil if passed or // the failure reason if does not. -func (app *Application) Validate(v interface{}) error { +func (app *Application) Validate(v any) error { if app.Validator == nil { return nil } - // val := reflect.ValueOf(v) - // if val.Kind() == reflect.Ptr && !val.IsNil() { - // val = val.Elem() - // } - - // if val.Kind() == reflect.Struct && val.Type() != timeType { - // return app.Validator.Struct(v) - // } - - // no need to check the kind, underline lib does it but in the future this may change (look above). err := app.Validator.Struct(v) if err != nil { if !strings.HasPrefix(err.Error(), "validator: ") { @@ -385,7 +357,7 @@ func Minify(ctx Context) { w := ctx.Application().Minifier().ResponseWriter(ctx.ResponseWriter().Naive(), ctx.Request()) // Note(@kataras): // We don't use defer w.Close() - // because this response writer holds a sync.WaitGroup under the hoods + // because this response writer holds a sync.WaitGroup under the hood // and we MUST be sure that its wg.Wait is called on request cancelation // and not in the end of handlers chain execution // (which if running a time-consuming task it will delay its resource release). @@ -421,7 +393,7 @@ func (app *Application) RegisterView(viewEngine view.Engine) { // // Use context.View to render templates to the client instead. // Returns an error on failure, otherwise nil. -func (app *Application) View(writer io.Writer, filename string, layout string, bindingData interface{}) error { +func (app *Application) View(writer io.Writer, filename string, layout string, bindingData any) error { if !app.view.Registered() { err := errors.New("view engine is missing, use `RegisterView`") app.logger.Error(err) @@ -628,15 +600,6 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor { return su } -// func (app *Application) OnShutdown(closers ...func()) { -// for _,cb := range closers { -// if cb == nil { -// continue -// } -// RegisterOnInterrupt(cb) -// } -// } - // Shutdown gracefully terminates all the application's server hosts and any tunnels. // Returns an error on the first failure, otherwise nil. func (app *Application) Shutdown(ctx stdContext.Context) error { @@ -678,7 +641,7 @@ func (app *Application) Shutdown(ctx stdContext.Context) error { // app.Logger().Errorf("%s: %s", typ, err) // }) func (app *Application) Build() error { - if app.builded { + if app.built { return nil } @@ -688,8 +651,7 @@ func (app *Application) Build() error { } } - // start := time.Now() - app.builded = true // even if fails. + app.built = true // even if fails. // check if a prior app.Logger().SetLevel called and if not // then set the defined configuration's log level. @@ -794,9 +756,6 @@ func (app *Application) Build() error { // app.RefreshRouter() } - // if end := time.Since(start); end.Seconds() > 5 { - // app.logger.Debugf("Application: build took %s", time.Since(start)) - return nil } @@ -1010,7 +969,7 @@ var ( ErrServerClosed = http.ErrServerClosed // ErrURLQuerySemicolon is logged by the standard net/http server when - // the request contains a semicolon (;) wihch, after go1.17 it's not used as a key-value separator character. + // the request contains a semicolon (;) which, after go1.17 it's not used as a key-value separator character. // // Ignore it by passing this error to the `iris.WithoutServerError` configurator // on `Application.Run/Listen` method. @@ -1216,7 +1175,9 @@ func (app *Application) tryStartTunneling() { app.setVHost(publicAddr[strings.Index(publicAddr, "://")+3:]) directLog := []byte(fmt.Sprintf("• Public Address: %s\n", publicAddr)) - app.logger.Printer.Write(directLog) // nolint:errcheck + if _, err := app.logger.Printer.Write(directLog); err != nil { + app.logger.Errorf("failed to write public address log: %v", err) + } }) }) } From e2dd93648790cc27fe12fb9d5b85c6006713ab9a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Mar 2026 06:36:05 +0000 Subject: [PATCH 6/6] Add Arabic RTL dashboard example for Iris New example demonstrating: - HTML view engine with RTL Arabic layout - Live stats via JSON API endpoint (requests, uptime, memory) - Registered routes viewer with color-coded HTTP methods - Responsive sidebar navigation - Pure CSS/JS without external dependencies Files: _examples/dashboard-ar/ https://claude.ai/code/session_01KpAo1Z18ekFZxaakKDitYF --- _examples/dashboard-ar/main.go | 113 ++++++++ _examples/dashboard-ar/static/css/style.css | 265 ++++++++++++++++++ _examples/dashboard-ar/static/js/app.js | 25 ++ _examples/dashboard-ar/views/index.html | 36 +++ .../dashboard-ar/views/layouts/main.html | 21 ++ .../dashboard-ar/views/partials/header.html | 6 + .../dashboard-ar/views/partials/sidebar.html | 16 ++ _examples/dashboard-ar/views/routes.html | 20 ++ 8 files changed, 502 insertions(+) create mode 100644 _examples/dashboard-ar/main.go create mode 100644 _examples/dashboard-ar/static/css/style.css create mode 100644 _examples/dashboard-ar/static/js/app.js create mode 100644 _examples/dashboard-ar/views/index.html create mode 100644 _examples/dashboard-ar/views/layouts/main.html create mode 100644 _examples/dashboard-ar/views/partials/header.html create mode 100644 _examples/dashboard-ar/views/partials/sidebar.html create mode 100644 _examples/dashboard-ar/views/routes.html diff --git a/_examples/dashboard-ar/main.go b/_examples/dashboard-ar/main.go new file mode 100644 index 000000000..6ce0b7c4d --- /dev/null +++ b/_examples/dashboard-ar/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "fmt" + "runtime" + "sync/atomic" + "time" + + "github.com/kataras/iris/v12" +) + +var ( + startTime = time.Now() + requestCount atomic.Int64 +) + +func main() { + app := iris.New() + + app.RegisterView(iris.HTML("./views", ".html")) + app.HandleDir("/static", "./static") + + // Middleware to count requests. + app.Use(func(ctx iris.Context) { + requestCount.Add(1) + ctx.Next() + }) + + app.Get("/", dashboardHandler) + app.Get("/routes", routesHandler) + app.Get("/api/stats", statsAPIHandler) + + app.Listen(":8080") +} + +func dashboardHandler(ctx iris.Context) { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + + data := iris.Map{ + "Title": "لوحة التحكم", + "Page": "dashboard", + "Version": iris.Version, + "Uptime": formatDuration(time.Since(startTime)), + "Requests": requestCount.Load(), + "RoutesCount": len(ctx.Application().GetRoutes()), + "MemoryMB": fmt.Sprintf("%.1f", float64(mem.Alloc)/1024/1024), + "GoVersion": runtime.Version(), + "NumGoroutine": runtime.NumGoroutine(), + } + + ctx.ViewLayout("layouts/main") + if err := ctx.View("index", data); err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + ctx.WriteString(err.Error()) + } +} + +func routesHandler(ctx iris.Context) { + type routeInfo struct { + Method string + Path string + Handler string + } + + routes := ctx.Application().GetRoutes() + routeList := make([]routeInfo, 0, len(routes)) + for _, r := range routes { + routeList = append(routeList, routeInfo{ + Method: r.Method, + Path: r.Path, + Handler: r.MainHandlerName, + }) + } + + data := iris.Map{ + "Title": "المسارات المسجلة", + "Page": "routes", + "Routes": routeList, + } + + ctx.ViewLayout("layouts/main") + if err := ctx.View("routes", data); err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + ctx.WriteString(err.Error()) + } +} + +func statsAPIHandler(ctx iris.Context) { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + + ctx.JSON(iris.Map{ + "uptime": formatDuration(time.Since(startTime)), + "requests": requestCount.Load(), + "memoryMB": fmt.Sprintf("%.1f", float64(mem.Alloc)/1024/1024), + "numGoroutine": runtime.NumGoroutine(), + }) +} + +func formatDuration(d time.Duration) string { + h := int(d.Hours()) + m := int(d.Minutes()) % 60 + s := int(d.Seconds()) % 60 + + if h > 0 { + return fmt.Sprintf("%d س %d د %d ث", h, m, s) + } + if m > 0 { + return fmt.Sprintf("%d د %d ث", m, s) + } + return fmt.Sprintf("%d ث", s) +} diff --git a/_examples/dashboard-ar/static/css/style.css b/_examples/dashboard-ar/static/css/style.css new file mode 100644 index 000000000..5175c26b6 --- /dev/null +++ b/_examples/dashboard-ar/static/css/style.css @@ -0,0 +1,265 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: "Segoe UI", Tahoma, Arial, sans-serif; + background: #f0f2f5; + color: #1a1a2e; + direction: rtl; +} + +.app { + display: flex; + min-height: 100vh; +} + +/* Sidebar */ +.sidebar { + width: 220px; + background: #1a1a2e; + color: #fff; + padding: 20px 0; + position: fixed; + top: 0; + right: 0; + height: 100vh; + overflow-y: auto; +} + +.sidebar-brand { + text-align: center; + padding: 10px 20px 30px; + border-bottom: 1px solid #2d2d50; +} + +.sidebar-brand h2 { + font-size: 1.8rem; + color: #00d2ff; +} + +.sidebar-version { + font-size: 0.8rem; + color: #888; +} + +.sidebar-nav { + padding: 15px 0; +} + +.nav-item { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 25px; + color: #ccc; + text-decoration: none; + transition: background 0.2s, color 0.2s; + font-size: 0.95rem; +} + +.nav-item:hover { + background: #2d2d50; + color: #fff; +} + +.nav-item.active { + background: #00d2ff22; + color: #00d2ff; + border-right: 3px solid #00d2ff; +} + +.nav-icon { + font-size: 1rem; +} + +/* Main content */ +.main { + flex: 1; + margin-right: 220px; +} + +.header { + background: #fff; + padding: 20px 30px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.header h1 { + font-size: 1.4rem; + color: #1a1a2e; +} + +.header-info { + font-size: 0.85rem; + color: #888; +} + +.content { + padding: 25px 30px; +} + +/* Cards */ +.cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.card { + background: #fff; + border-radius: 10px; + padding: 25px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + transition: transform 0.2s; +} + +.card:hover { + transform: translateY(-2px); +} + +.card-label { + font-size: 0.85rem; + color: #888; + margin-bottom: 8px; +} + +.card-value { + font-size: 1.8rem; + font-weight: 700; + color: #1a1a2e; +} + +/* Info section */ +.info-section { + background: #fff; + border-radius: 10px; + padding: 25px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); +} + +.info-section h3 { + margin-bottom: 15px; + color: #1a1a2e; + font-size: 1.1rem; +} + +.info-table { + width: 100%; + border-collapse: collapse; +} + +.info-table tr { + border-bottom: 1px solid #f0f2f5; +} + +.info-table td { + padding: 10px 0; + font-size: 0.9rem; +} + +.info-label { + color: #888; + width: 200px; +} + +/* Routes table */ +.table-container { + background: #fff; + border-radius: 10px; + padding: 25px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + overflow-x: auto; +} + +.routes-table { + width: 100%; + border-collapse: collapse; +} + +.routes-table th { + text-align: right; + padding: 12px 15px; + background: #f8f9fa; + color: #666; + font-size: 0.85rem; + font-weight: 600; + border-bottom: 2px solid #e9ecef; +} + +.routes-table td { + padding: 10px 15px; + border-bottom: 1px solid #f0f2f5; + font-size: 0.9rem; +} + +.routes-table tr:hover { + background: #f8f9fa; +} + +.method { + display: inline-block; + padding: 3px 10px; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 700; + color: #fff; + min-width: 55px; + text-align: center; + font-family: monospace; +} + +.method-GET { background: #28a745; } +.method-POST { background: #007bff; } +.method-PUT { background: #fd7e14; } +.method-DELETE { background: #dc3545; } +.method-PATCH { background: #6f42c1; } +.method-HEAD { background: #6c757d; } +.method-OPTIONS { background: #17a2b8; } + +.route-path { + font-family: monospace; + color: #1a1a2e; +} + +.route-handler { + font-family: monospace; + font-size: 0.8rem; + color: #888; +} + +/* Responsive */ +@media (max-width: 768px) { + .sidebar { + width: 60px; + padding: 10px 0; + } + + .sidebar-brand h2 { + font-size: 1rem; + } + + .sidebar-version, + .nav-item span:not(.nav-icon) { + display: none; + } + + .nav-item { + justify-content: center; + padding: 12px; + } + + .main { + margin-right: 60px; + } + + .cards { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/_examples/dashboard-ar/static/js/app.js b/_examples/dashboard-ar/static/js/app.js new file mode 100644 index 000000000..483545daa --- /dev/null +++ b/_examples/dashboard-ar/static/js/app.js @@ -0,0 +1,25 @@ +(function () { + function updateStats() { + fetch("/api/stats") + .then(function (res) { return res.json(); }) + .then(function (data) { + var el; + + el = document.getElementById("stat-requests"); + if (el) el.textContent = data.requests; + + el = document.getElementById("stat-uptime"); + if (el) el.textContent = data.uptime; + + el = document.getElementById("stat-memory"); + if (el) el.textContent = data.memoryMB + " MB"; + + el = document.getElementById("stat-goroutines"); + if (el) el.textContent = data.numGoroutine; + }) + .catch(function () {}); + } + + // Update every 5 seconds. + setInterval(updateStats, 5000); +})(); diff --git a/_examples/dashboard-ar/views/index.html b/_examples/dashboard-ar/views/index.html new file mode 100644 index 000000000..5bde2b926 --- /dev/null +++ b/_examples/dashboard-ar/views/index.html @@ -0,0 +1,36 @@ +
+
+
اجمالي الطلبات
+
{{.Requests}}
+
+
+
وقت التشغيل
+
{{.Uptime}}
+
+
+
المسارات المسجلة
+
{{.RoutesCount}}
+
+
+
استخدام الذاكرة
+
{{.MemoryMB}} MB
+
+
+ +
+

معلومات النظام

+ + + + + + + + + + + + + +
اصدار Iris{{.Version}}
اصدار Go{{.GoVersion}}
عدد الـ Goroutines{{.NumGoroutine}}
+
diff --git a/_examples/dashboard-ar/views/layouts/main.html b/_examples/dashboard-ar/views/layouts/main.html new file mode 100644 index 000000000..6197d1c5f --- /dev/null +++ b/_examples/dashboard-ar/views/layouts/main.html @@ -0,0 +1,21 @@ + + + + + + {{.Title}} - Iris Dashboard + + + +
+ {{ render "partials/sidebar.html" . }} +
+ {{ render "partials/header.html" . }} +
+ {{ yield . }} +
+
+
+ + + diff --git a/_examples/dashboard-ar/views/partials/header.html b/_examples/dashboard-ar/views/partials/header.html new file mode 100644 index 000000000..9c41eaf99 --- /dev/null +++ b/_examples/dashboard-ar/views/partials/header.html @@ -0,0 +1,6 @@ +
+

{{.Title}}

+
+ Go {{.GoVersion}} +
+
diff --git a/_examples/dashboard-ar/views/partials/sidebar.html b/_examples/dashboard-ar/views/partials/sidebar.html new file mode 100644 index 000000000..a24457b86 --- /dev/null +++ b/_examples/dashboard-ar/views/partials/sidebar.html @@ -0,0 +1,16 @@ + diff --git a/_examples/dashboard-ar/views/routes.html b/_examples/dashboard-ar/views/routes.html new file mode 100644 index 000000000..86a1f303c --- /dev/null +++ b/_examples/dashboard-ar/views/routes.html @@ -0,0 +1,20 @@ +
+ + + + + + + + + + {{range .Routes}} + + + + + + {{end}} + +
الطريقةالمسارالمعالج
{{.Method}}{{.Path}}{{.Handler}}
+