إذا كنت تعمل في شركة خدمات أو استشارات أو في فريق تطوير يحاسب عملاءه بالساعة، فأنت تعرف السؤال الذي يأتي في نهاية كل شهر: كم ساعة أخذ مشروع هذا العميل فعلاً؟ والحل الأول الذي يبدأ به الجميع هو ملف Excel يكتب فيه كل موظف ساعاته، أو تقدير من الذاكرة يوم إصدار الفاتورة، ولكن مع الوقت سوف تجد أن كل موظف يكتب بطريقته، وأن نصف الساعات تسجل بعد أيام من العمل فتأتي تقريبية، وأن لا أحد يعرف إن كان الفريق قد تجاوز ميزانية المشروع إلا بعد أن يتجاوزها، والسبب أن الملف لا يربط الوقت بالمشروع والعميل والمهمة Task، ولا يعطيك تقريراً واحداً تثق به.
لذلك ينتقل الفريق عادة إلى الحل الثاني، وهو خدمة سحابية لتتبع الوقت Time Tracking مثل Toggl أو Harvest، وهذا يحل مشكلة الفوضى، ولكنه يأتي بمشكلة أخرى، وهي أن ساعات موظفيك وأسماء عملائك وأسعارك أصبحت على سيرفرات Servers شركة أخرى، وأنك تدفع اشتراكاً Subscription عن كل مستخدم، وكلما كبر الفريق كبرت الفاتورة.
والحل الأنسب في رأينا هو أن تشغل أداة تتبع الوقت على سيرفرك، وهنا يأتي دور Titra، وهو أداة مفتوحة المصدر Open Source تسجل فيها الوقت يدوياً أو بساعة إيقاف Stopwatch، وتربطه بمشروع وعميل ومهمة، وتحدد لكل مشروع السعر بالساعة Hourly Rate وهدفاً للساعات، ثم تعطيك تقارير Reports تفصيلية ويومية وإجمالية مع تصدير Export إلى CSV وExcel، وفيه فرق عمل Teams بصلاحيات Permissions، ودخول عبر LDAP أو OpenID Connect، وواجهة API.
ويناسب Titra شركات الخدمات والاستشارات وفرق التطوير الصغيرة والمتوسطة التي تصدر فواتيرها Invoices بالساعة، أو تريد أن تعرف أين يذهب وقتها، ولكنه ليس نظاماً للموارد البشرية HR ولا للرواتب Payroll، فهو يعرض ساعات العمل اليومية ولكنه لا يحتسب الحضور والانصراف بالمعنى النظامي.
وسوف نناقش في هذا المقال ما يلي:
- أي إصدار Release من Titra نثبت، ولماذا لا نعتمد على أحدث إصدار حالياً.
- تثبيت Titra مع MongoDB عبر Docker Compose خلف الـ Reverse Proxy، ومعنى كل متغير في الإعداد.
- تسجيل المدير الأول ثم إغلاق التسجيل المفتوح من الواجهة ومن جهة السيرفر، لأن خيار الواجهة وحده لا يكفي.
- إنشاء مشروع وتسجيل الوقت وقراءة التقارير.
- النسخ الاحتياطي والاستعادة والتحديث، وأشهر المشكلات وحلولها.
ما تحتاجه قبل أن تبدأ
- سيرفر مثبت عليه Docker وCompose، وإذا لم تثبته بعد فابدأ بدليل تثبيت Docker على Ubuntu.
- الموارد Resources: يستهلك Titra قرابة 125 ميجابايت من الذاكرة RAM، ويستهلك MongoDB قرابة 150 ميجابايت، ولاحظ أن MongoDB 5 وما بعده يحتاج إلى معالج يدعم تعليمات AVX، وكل السيرفرات الحديثة تدعمها، ولكن بعض المحاكيات Emulators القديمة لا تدعمها.
- نطاق Domain مثل
time.example.com، وReverse Proxy يعمل بـ HTTPS ومتصل بشبكة Docker باسمproxyمثل Nginx Proxy Manager، ولأن Titra مبني على Meteor ويستخدم WebSockets فيجب أن تفعلها في الـ Reverse Proxy. - حساب SMTP إذا أردت أن يستعيد المستخدمون كلمات المرور بأنفسهم.
أي إصدار من Titra نثبت؟
نقل المشروع مستودعه Repository على GitHub من kromitgmbh/titra إلى titraio/titra، فأصبح اسم ال Docker Image الحالية هو titraio/titra، والاسم القديم kromit/titra ما زال يستقبل الوسوم Tags نفسها، وأحدث إصدار حتى أكتوبر 2026 هو v1.1.0 الذي صدر في 17 سبتمبر 2026 ومعه تحسينات أمنية Security Fixes.
وقد تتوقع أن الصحيح هو أن تثبت أحدث إصدار دائماً، ولكن إنشاء أي مشروع يفشل في هذا الإصدار، فتضغط Save ولا يحدث شيء، ويتلقى المتصفح الخطأ Internal server error [500]، وتعديل مشروع موجود يفشل بالخطأ نفسه، والسبب في الكود Source Code: الدالتان Functions createProject وupdateProject في الإصدار الجديد تستدعيان buildSafePayload وprojectAllowedFields دون أن تستوردهما Import، فيظهر في سجل الـ Container السطر ReferenceError: buildSafePayload is not defined، أما الإصدار السابق v1.0.12 فيعمل دون أي مشكلة، ولذلك يعتمد عليه هذا الدليل.
تثبيت Titra بـ Docker Compose
نبدأ بإنشاء مجلد للخدمة يحفظ فيه ملف Compose وملف الإعدادات:
sudo mkdir -p /opt/titra
sudo chown $USER: /opt/titra
cd /opt/titraبعد ذلك سوف ننشئ ملف .env الذي يحفظ العنوان الرئيسي للموقع Root URL وبيانات البريد، والبريد اختياري، ولاحظ أن قيمة MAIL_URL تتبع صيغة Meteor، فإذا كانت في كلمة المرور رموز خاصة فقم بترميزها بصيغة الروابط URL Encoding:
cat > .env <<'EOF'
ROOT_URL=https://time.example.com
MAIL_URL=smtps://titra%40example.com:ضع-كلمة-المرور@smtp.example.com:465
EOF
chmod 600 .envالآن سوف ننشئ الملف /opt/titra/compose.yaml:
services:
titra:
image: titraio/titra:v1.0.12
container_name: titra
restart: unless-stopped
depends_on:
mongodb:
condition: service_healthy
environment:
ROOT_URL: ${ROOT_URL}
MONGO_URL: mongodb://mongodb:27017/titra?directConnection=true
MAIL_URL: ${MAIL_URL}
PORT: 3000
networks:
- internal
- proxy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
mongodb:
image: mongo:8.0.32
container_name: titra-db
restart: unless-stopped
volumes:
- titra_db:/data/db
networks:
- internal
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
volumes:
titra_db:
name: titra_db
networks:
internal: {}
proxy:
external: trueفي الإعداد أعلاه لاحظ التالي:
- ROOT_URL: يجب أن يطابق النطاق الذي يفتحه المستخدمون حرفاً بحرف ومعه
https://، والسبب أن Titra يبني منه الروابط واتصال WebSocket، وأي اختلاف بسيط يجعل الصفحة لا تكتمل. - MongoDB 8.0: ملف Compose الرسمي للمشروع يستخدم الإصدار 7.0، وTitra 1.0.12 يعمل على 8.0.32 دون مشكلة، واخترنا الفرع 8.0 لأنه إصدار رئيسي Major Release له دورة دعم مدتها خمس سنوات، أما 8.1 و8.2 و8.3 فإصدارات فرعية Minor Releases يتوقف إصلاح كل واحد منها عندما يصدر الذي بعده.
- لماذا لا توجد مصادقة Authentication على MongoDB؟ لأن القاعدة على شبكة
internalلا يصل إليها غير Titra، ولا ننشر منفذها Port، وإذا أردت حماية إضافية فأضفMONGO_INITDB_ROOT_USERNAMEوMONGO_INITDB_ROOT_PASSWORDكما يشرح توثيق ال Docker Image الخاصة بـ mongo، ثم أنشئ مستخدماً لقاعدةtitraوضع بياناته فيMONGO_URL، وفي كل الأحوال لا تقم بنشر المنفذ27017على الانترنت. - لا ينشر الملف أي منفذ لـ Titra نفسه، فالـ Reverse Proxy يصل إلى
titra:3000عبر شبكةproxy.
الآن نقوم بإنشاء شبكة الـ Reverse Proxy إذا لم تكن موجودة، ثم تشغيل الخدمة ومتابعة السجلات Logs:
docker network create proxy
docker compose up -d
docker compose logs -f titraوالمخرج سوف يكون كما يلي:
titra | Connecting to MongoDB...
titra | Successfully connected to MongoDB
titra | Starting titra...
titra | titra started on port 3000بعد ذلك أنشئ في الـ Reverse Proxy مضيفاً Proxy Host للنطاق time.example.com يوجه الطلبات إلى http://titra:3000، وفعل فيه Websockets Support وشهادة Certificate من Let's Encrypt وخيار Force SSL، وإذا نسيت تفعيل WebSockets فسوف تبقى الصفحة في حالة تحميل ولا تكتمل.
حساب المدير الأول وإغلاق التسجيل المفتوح
افتح https://time.example.com واضغط Register، ولاحظ أن أول من يسجل يصبح مدير النظام Administrator، لذلك سجل حسابك مباشرة بعد التشغيل قبل أن يسبقك إليه أحد:

بعد التسجيل ينشئ Titra مشروعاً تجريبياً باسمك، والآن عليك أن تغلق التسجيل المفتوح Registration، فافتح قائمة المستخدم (الأحرف الأولى من اسمك أعلى يمين الصفحة)، واختر Administration ثم Global settings، وفعل في قسم Login خيار Disable new user registration، وبعدها يضيف المدير المستخدمين من تبويب Users.
وفي القسم نفسه خيارات LDAP وOpenID Connect لربط الدخول بنظام الدخول الموحد SSO، وخيار الوصول المجهول Anonymous Access عبر /try، فأبق هذا الخيار معطلاً في بيئة الإنتاج Production:

وقد يتساءل البعض: هل يكفي هذا الخيار لإغلاق التسجيل؟ والإجابة لا، والسبب أن Titra يستخدم الخيار في الواجهة فقط، فهو يخفي نموذج التسجيل ويعرض مكانه تحذيراً، ولكن السيرفر نفسه ما زال يقبل طلب إنشاء الحساب createUser الذي يرسله أي متصفح عبر اتصال WebSocket، وعند تجربة ذلك على Titra 1.0.12 بعد تفعيل الخيار أنشأ السيرفر الحساب الجديد وأعاد له رمز دخول Token كأن شيئاً لم يكن، وهذا يعني أن أي شخص يعرف كيف يعمل Meteor يستطيع أن يسجل في نظامك ويرى المشاريع العامة فيه.
والحل الأنسب هو أن تغلق التسجيل من جهة السيرفر أيضاً بالخيار forbidClientAccountCreation من إعدادات الحسابات في Meteor، والذي تمرره إلى الـ Container في متغير البيئة METEOR_SETTINGS، فأضف السطر الأخير التالي إلى قسم environment للخدمة titra في compose.yaml:
environment:
ROOT_URL: ${ROOT_URL}
MONGO_URL: mongodb://mongodb:27017/titra?directConnection=true
MAIL_URL: ${MAIL_URL}
PORT: 3000
METEOR_SETTINGS: '{"packages":{"accounts-base":{"forbidClientAccountCreation":true}}}'ثم أعد إنشاء الـ Container:
docker compose up -dفي الإعداد أعلاه لاحظ التالي:
- أي طلب تسجيل من المتصفح سوف يرفضه السيرفر بالخطأ
Signups forbidden [403]، سواءً جاء من نموذج التسجيل أو مباشرة عبر WebSocket. - إضافة المستخدمين من تبويب Users في الإدارة تبقى تعمل، لأنها تنشئ الحساب على السيرفر وليس من المتصفح، وكذلك الحسابات التي ينشئها الدخول عبر LDAP أو OpenID Connect.
- لا تقم بإضافة هذا السطر قبل أن تسجل حساب المدير، لأنه يمنع التسجيل الأول أيضاً.
- إذا كان التسجيل مفتوحاً مدة من الوقت قبل أن تغلقه، فراجع قائمة Users واحذف أي حساب لا تعرفه.
وفي صفحة Global settings نفسها إعدادات أخرى تستحق المراجعة، مثل وحدة العملة Cost unit، وتنسيق التاريخ Date Format، وبداية الأسبوع (ضع 0 إذا كان أسبوع العمل عندكم يبدأ يوم الأحد)، وعدد ساعات يوم العمل، وعنوان المرسل للبريد.
إنشاء أول مشروع
اضغط + Project من صفحة Overview، وأهم الحقول Fields هي اسم المشروع، ولونه في الرسوم البيانية Charts، والعميل الذي تتجمع التقارير بحسبه، والمهمة الافتراضية، وفي قسم Billing & Budget تحدد السعر بالساعة وهدف الساعات، فيعرض Titra تقدمك نحو الهدف في شريط تقدم Progress Bar على بطاقة المشروع:

ولكي تشرك زملاءك في المشروع، افتحه للتعديل وأضفهم إلى فريقه مع أدوارهم Roles، أو اجعل المشروع عاماً Public لكل المستخدمين، إلا إذا قمت بتعطيل المشاريع العامة من الإعدادات.
تسجيل الوقت وقراءة التقارير
زر Track time يفتح نموذج اليوم، فتختار المشروع وتكتب المهمة وسوف يقترح عليك Titra مهامك السابقة، ثم تدخل الساعات بالكسور العشرية Decimal، فالقيمة 2.5 تعني ساعتين ونصفاً، أو تشغل ساعة الإيقاف وتحفظ عند الانتهاء، ومجموع اليوم يظهر أعلى النموذج، وفيه تبويبا Week وMonth لإدخال وقت عدة أيام دفعة واحدة:

أما صفحة Details فتعرض الإدخالات Time Entries مع مرشحات Filters بحسب المشروع والفترة والشخص والعميل، وفيها تبويبات للعرض اليومي والإجمالي وساعات العمل والرسوم البيانية، وأزرار للتصدير إلى CSV وExcel ولتعليم الإدخالات بأنها مفوترة Billable:

وفي صفحة Overview تجد ملخصاً لكل مشروع يعرض الوقت الكلي والهدف والإيراد Revenue المحسوب من السعر، مع توزيع المهام والسجل الزمني لثلاثة أشهر:

ولاحظ أن الإيراد يظهر افتراضياً بعلامة €، فقم بتغييرها إلى عملتك من Cost unit في الإعدادات العامة.
كيف تتأكد أن كل شيء يعمل
- يعرض الأمر
docker compose psالـ Containers الاثنين بحالةhealthy، وتجد في السجل السطرtitra started on port 3000. - يفتح
https://time.example.comوتكتمل الصفحة، وهذا يعني أن WebSocket يعمل، وفي أدوات المطور Developer Tools سوف ترى اتصال/sockjs/.../websocketبالحالة 101. - ينقلك إنشاء مشروع إلى صفحة تعديله (
/edit/project/...)، ويرفع إدخال الوقت مجموع اليوم. - بعد تفعيل Disable new user registration وإضافة
METEOR_SETTINGS، افتح صفحة التسجيل من نافذة خاصة في المتصفح، وسوف تجد مكان النموذج تحذيراً بأن التسجيل معطل. - البيانات محفوظة في MongoDB، والأمر
docker compose exec mongodb mongosh titra --quiet --eval 'db.timecards.countDocuments()'يعيد عدد الإدخالات.
النسخ الاحتياطي Backup والاستعادة Restore
كل بيانات Titra موجودة في قاعدة titra على MongoDB، لذلك انسخها مع الملفين compose.yaml و.env، والأداة mongodump موجودة أصلاً في ال Docker Image الخاصة بـ mongo، وهي تخرج أرشيفاً مضغوطاً Compressed Archive إلى المخرج القياسي stdout:
cd /opt/titra
mkdir -p backups
docker compose exec -T mongodb mongodump --db titra --archive --gzip > backups/titra-$(date +%F).archive.gzوللاستعادة على سيرفر جديد، قم بتشغيل القاعدة وحدها، ثم انسخ الأرشيف إليها واستعده بالأداة mongorestore، ثم شغل التطبيق، ولاحظ أن الخيار --drop يحذف المجموعات Collections الموجودة قبل الاستعادة:
docker compose up -d mongodb
docker compose exec -T mongodb sh -c 'cat > /tmp/titra.archive.gz' < backups/titra-2026-09-26.archive.gz
docker compose exec -T mongodb mongorestore --archive=/tmp/titra.archive.gz --gzip --drop
docker compose up -dوالنسخة الاحتياطية التي لم تجرب استعادتها لا تعرف إن كانت تعمل، لذلك اختبرها دون أن تمس القاعدة العاملة باستعادة الأرشيف إلى قاعدة باسم آخر بالخيارين --nsFrom="titra.*" --nsTo="restoretest.*"، ثم قارن عدد الإدخالات فيها بالقاعدة الأصلية.
التحديث Upgrade إلى إصدار أحدث
- اقرأ ملاحظات الإصدار Release Notes، وقم بتشغيل الإصدار الجديد على نسخة اختبار مستعادة من النسخة الاحتياطية، وأنشئ فيها مشروعاً وعدله، وسجل وقتاً، وافتح التقارير قبل أن تحدث بيئة الإنتاج.
- خذ نسخة احتياطية بالأداة
mongodump. - غير الوسم في
compose.yaml، ثم نفذ الأوامر التالية:
cd /opt/titra
docker compose pull
docker compose up -d
docker compose logs --tail 20 titraوللرجوع إلى الإصدار السابق أعد الوسم القديم، وإذا كان الإصدار الجديد قد عدل البيانات فاستعد النسخة الاحتياطية أيضاً.
أما MongoDB فتحديثه داخل الفرع Branch نفسه يكفيه تغيير الوسم، مثل الانتقال من 8.0.32 إلى إصدار 8.0.x التالي، ولكن الانتقال بين الإصدارات الرئيسية يكون على مراحل، أي من 7.0 إلى 8.0 وليس من 5.0 إلى 8.0 مباشرة، وبعد كل مرحلة ترفع featureCompatibilityVersion، والطريق الأبسط أن تأخذ نسخة بالأداة mongodump من القاعدة القديمة، ثم تستعيدها بالأداة mongorestore في قاعدة جديدة بالإصدار الجديد.
مشكلات شائعة وحلولها
زر Save في مشروع جديد لا يفعل أي شيء
إذا كنت على v1.1.0 فهذا هو الخلل Bug الذي شرحناه في البداية، وسوف تجد الخطأ Internal server error [500] في أدوات المطور، والحل أن ترجع إلى v1.0.12 أو تنتقل إلى إصدار يصلحه، والتبديل بين الإصدارين لا يمس البيانات.
الصفحة تبقى في حالة تحميل خلف الـ Reverse Proxy
السبب واحد من اثنين: إما أن WebSockets غير مفعلة في الـ Reverse Proxy، أو أن ROOT_URL لا يطابق النطاق، كأن يبدأ بـ http:// بدلاً من https://، فقم بتصحيح الخطأ ثم أعد إنشاء الـ Container.
MongoDB يتوقف فور التشغيل
إذا ظهر في سجل الـ Container أن المعالج CPU لا يدعم AVX، فالسيرفر أو المحاكي قديم، والحل سيرفر بمعالج أحدث أو نمط محاكاة يمرر تعليمات المعالج (host CPU)، أما البقاء على MongoDB 4.4 فهو إصدار لم يعد مدعوماً ولا ننصح به.
Titra لا يتصل بـ MongoDB
تأكد أن الخدمتين على شبكة internal نفسها، وأن MONGO_URL يشير إلى اسم الخدمة mongodb، ولاحظ أن الخيار directConnection=true مطلوب مع سيرفر MongoDB مفرد لا ينتمي إلى مجموعة نسخ متماثلة Replica Set.
نسيت كلمة المرور ولا يوجد بريد مضبوط
اضبط MAIL_URL وأعد إنشاء الـ Container، ثم استخدم Forgot your password من صفحة الدخول، وإذا كان الحساب لمستخدم عادي فالمدير يستطيع إدارته من Administration ثم Users.
التواريخ تظهر بيوم خاطئ
راجع المنطقة الزمنية Time Zone في ملف المستخدم، وتنسيق التاريخ وبداية الأسبوع في الإعدادات العامة، والسبب أن Titra يحفظ التواريخ بتوقيت UTC ويعرضها بالمنطقة الزمنية للمتصفح.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- ملف Excel لا يربط الوقت بالمشروع والعميل، والخدمات السحابية تحل ذلك ولكن بيانات فريقك تبقى عند شركة أخرى وتدفع عن كل مستخدم، وTitra يعطيك التتبع والتقارير وبياناتك في MongoDB على سيرفرك.
- ثبت
v1.0.12حتى يصدر إصلاح بعدv1.1.0، لأن إنشاء المشاريع وتعديلها يفشل في 1.1.0. - اجعل
ROOT_URLمطابقاً للنطاق معhttps://، وفعل WebSockets في الـ Reverse Proxy. - سجل حساب المدير فور التشغيل، ثم أغلق التسجيل من الواجهة ومن السيرفر معاً بـ
forbidClientAccountCreationفيMETEOR_SETTINGS، لأن خيار الواجهة وحده يخفي النموذج فقط. - لا تقم بنشر منفذ MongoDB، وخذ نسخة بـ
mongodumpوجرب استعادتها، وخذ نسخة قبل كل تحديث.
سجل التحديثات
- سبتمبر 2026: كتابة الدليل واختباره على Titra v1.0.12 مع MongoDB 8.0.32 (مع تجربة v1.1.0 وتوثيق خلل إنشاء المشاريع فيه).
- أكتوبر 2026: مراجعة الدليل والتأكد أن
v1.1.0ما زال أحدث إصدار وأن الخلل ما زال فيه ويشمل تعديل المشاريع أيضاً، وإضافة إغلاق التسجيل من جهة السيرفر بـMETEOR_SETTINGSلأن خيار Disable new user registration يخفي النموذج فقط، وتصحيح تصنيف إصدارات MongoDB الفرعية.