@escalated-dev/escalated 0.4.0 → 0.6.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.
- package/README.md +45 -1
- package/package.json +14 -5
- package/src/components/ActivityTimeline.vue +66 -66
- package/src/components/AssigneeSelect.vue +23 -23
- package/src/components/EscalatedLayout.vue +35 -11
- package/src/components/FileDropzone.vue +41 -41
- package/src/components/PinnedNotes.vue +6 -3
- package/src/components/PluginSlot.vue +62 -0
- package/src/components/PriorityBadge.vue +39 -39
- package/src/components/ReplyComposer.vue +159 -159
- package/src/components/ReplyThread.vue +9 -6
- package/src/components/SlaTimer.vue +49 -49
- package/src/components/StatusBadge.vue +46 -46
- package/src/components/TagSelect.vue +53 -53
- package/src/composables/useI18n.js +80 -0
- package/src/composables/usePluginExtensions.js +67 -0
- package/src/defineEscalatedPlugin.js +50 -0
- package/src/index.js +5 -0
- package/src/locales/de.json +442 -0
- package/src/locales/en.json +442 -0
- package/src/locales/es.json +442 -0
- package/src/locales/fr.json +442 -0
- package/src/locales/index.js +6 -0
- package/src/pages/Admin/ApiTokens/Index.vue +217 -0
- package/src/pages/Admin/Plugins/Index.vue +197 -0
- package/src/pages/Admin/Plugins.vue +129 -0
- package/src/pages/Admin/Tickets/Show.vue +12 -1
- package/src/pages/Agent/Dashboard.vue +31 -1
- package/src/pages/Agent/TicketShow.vue +12 -1
- package/src/utils/sanitizeHtml.js +64 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nav": {
|
|
3
|
+
"admin": "Administration",
|
|
4
|
+
"admin_badge": "ADMIN",
|
|
5
|
+
"agent_panel": "Panneau agent",
|
|
6
|
+
"back_to_app": "Retour à l'application",
|
|
7
|
+
"my_tickets": "Mes tickets",
|
|
8
|
+
"dashboard": "Tableau de bord",
|
|
9
|
+
"tickets": "Tickets",
|
|
10
|
+
"reports": "Rapports",
|
|
11
|
+
"departments": "Départements",
|
|
12
|
+
"sla_policies": "Politiques SLA",
|
|
13
|
+
"escalation_rules": "Règles d'escalade",
|
|
14
|
+
"tags": "Étiquettes",
|
|
15
|
+
"canned_responses": "Réponses prédéfinies",
|
|
16
|
+
"macros": "Macros",
|
|
17
|
+
"api_tokens": "Jetons API",
|
|
18
|
+
"plugins": "Extensions",
|
|
19
|
+
"settings": "Paramètres"
|
|
20
|
+
},
|
|
21
|
+
"status": {
|
|
22
|
+
"open": "Ouvert",
|
|
23
|
+
"in_progress": "En cours",
|
|
24
|
+
"waiting_on_customer": "En attente du client",
|
|
25
|
+
"waiting_on_agent": "En attente de l'agent",
|
|
26
|
+
"escalated": "Escaladé",
|
|
27
|
+
"resolved": "Résolu",
|
|
28
|
+
"closed": "Fermé",
|
|
29
|
+
"reopened": "Rouvert"
|
|
30
|
+
},
|
|
31
|
+
"priority": {
|
|
32
|
+
"low": "Basse",
|
|
33
|
+
"medium": "Moyenne",
|
|
34
|
+
"high": "Haute",
|
|
35
|
+
"urgent": "Urgente",
|
|
36
|
+
"critical": "Critique"
|
|
37
|
+
},
|
|
38
|
+
"activity": {
|
|
39
|
+
"status_changed": "a changé le statut",
|
|
40
|
+
"assigned": "a assigné le ticket",
|
|
41
|
+
"unassigned": "a désassigné le ticket",
|
|
42
|
+
"priority_changed": "a changé la priorité",
|
|
43
|
+
"tag_added": "a ajouté une étiquette",
|
|
44
|
+
"tag_removed": "a retiré une étiquette",
|
|
45
|
+
"escalated": "a escaladé le ticket",
|
|
46
|
+
"sla_breached": "SLA dépassé",
|
|
47
|
+
"replied": "a répondu",
|
|
48
|
+
"note_added": "a ajouté une note",
|
|
49
|
+
"department_changed": "a changé le département",
|
|
50
|
+
"reopened": "a rouvert le ticket",
|
|
51
|
+
"resolved": "a résolu le ticket",
|
|
52
|
+
"closed": "a fermé le ticket",
|
|
53
|
+
"system": "Système",
|
|
54
|
+
"no_activity": "Aucune activité pour le moment.",
|
|
55
|
+
"to": "à",
|
|
56
|
+
"assigned_to": "assigné à"
|
|
57
|
+
},
|
|
58
|
+
"ticket": {
|
|
59
|
+
"reference": "Référence",
|
|
60
|
+
"subject": "Sujet",
|
|
61
|
+
"requester": "Demandeur",
|
|
62
|
+
"status": "Statut",
|
|
63
|
+
"priority": "Priorité",
|
|
64
|
+
"assignee": "Assigné à",
|
|
65
|
+
"last_reply": "Dernière réponse",
|
|
66
|
+
"unknown": "Inconnu",
|
|
67
|
+
"unassigned": "Non assigné",
|
|
68
|
+
"no_tickets": "Aucun ticket trouvé.",
|
|
69
|
+
"details": "Détails",
|
|
70
|
+
"department": "Département",
|
|
71
|
+
"created": "Créé",
|
|
72
|
+
"activity": "Activité",
|
|
73
|
+
"conversation": "Conversation",
|
|
74
|
+
"by": "par",
|
|
75
|
+
"assign_to_me": "M'assigner",
|
|
76
|
+
"close_ticket": "Fermer le ticket",
|
|
77
|
+
"reopen": "Rouvrir",
|
|
78
|
+
"all_tickets": "Tous les tickets",
|
|
79
|
+
"ticket_queue": "File d'attente des tickets",
|
|
80
|
+
"my_tickets": "Mes tickets",
|
|
81
|
+
"new_ticket": "Nouveau ticket",
|
|
82
|
+
"create_ticket": "Créer un ticket",
|
|
83
|
+
"submit_ticket": "Soumettre le ticket",
|
|
84
|
+
"no_replies": "Aucune réponse pour le moment.",
|
|
85
|
+
"ticket_closed_no_replies": "Ce ticket est fermé. Aucune réponse supplémentaire ne peut être ajoutée."
|
|
86
|
+
},
|
|
87
|
+
"bulk": {
|
|
88
|
+
"tickets_selected": ":count ticket(s) sélectionné(s)",
|
|
89
|
+
"status_placeholder": "Statut...",
|
|
90
|
+
"priority_placeholder": "Priorité...",
|
|
91
|
+
"assign_placeholder": "Assigner à...",
|
|
92
|
+
"department_placeholder": "Département...",
|
|
93
|
+
"delete": "Supprimer",
|
|
94
|
+
"deselect_all": "Tout désélectionner"
|
|
95
|
+
},
|
|
96
|
+
"filter": {
|
|
97
|
+
"search_tickets": "Rechercher des tickets...",
|
|
98
|
+
"all_statuses": "Tous les statuts",
|
|
99
|
+
"all_priorities": "Toutes les priorités",
|
|
100
|
+
"all_agents": "Tous les agents",
|
|
101
|
+
"all_departments": "Tous les départements",
|
|
102
|
+
"following": "Suivis"
|
|
103
|
+
},
|
|
104
|
+
"quick_filter": {
|
|
105
|
+
"my_tickets": "Mes tickets",
|
|
106
|
+
"unassigned": "Non assignés",
|
|
107
|
+
"urgent_plus": "Urgent+",
|
|
108
|
+
"sla_breaching": "SLA en dépassement",
|
|
109
|
+
"following": "Suivis"
|
|
110
|
+
},
|
|
111
|
+
"reply": {
|
|
112
|
+
"reply": "Répondre",
|
|
113
|
+
"internal_note": "Note interne",
|
|
114
|
+
"note_visibility": "Cette note est visible uniquement par les agents.",
|
|
115
|
+
"placeholder": "Saisissez votre réponse...",
|
|
116
|
+
"canned_responses": "Réponses prédéfinies...",
|
|
117
|
+
"add_note": "Ajouter une note",
|
|
118
|
+
"send_reply": "Envoyer la réponse",
|
|
119
|
+
"pinned": "Épinglé",
|
|
120
|
+
"unpin": "Désépingler",
|
|
121
|
+
"pin": "Épingler",
|
|
122
|
+
"write_internal_note": "Rédiger une note interne..."
|
|
123
|
+
},
|
|
124
|
+
"rating": {
|
|
125
|
+
"customer_rating": "Évaluation du client",
|
|
126
|
+
"how_was_experience": "Comment était votre expérience ?",
|
|
127
|
+
"terrible": "Terrible",
|
|
128
|
+
"poor": "Mauvaise",
|
|
129
|
+
"okay": "Correcte",
|
|
130
|
+
"good": "Bonne",
|
|
131
|
+
"excellent": "Excellente",
|
|
132
|
+
"feedback_placeholder": "Des commentaires supplémentaires ? (facultatif)",
|
|
133
|
+
"thank_you": "Merci pour votre retour !",
|
|
134
|
+
"submit_rating": "Soumettre l'évaluation",
|
|
135
|
+
"customer_satisfaction": "Satisfaction client"
|
|
136
|
+
},
|
|
137
|
+
"keyboard": {
|
|
138
|
+
"title": "Raccourcis clavier",
|
|
139
|
+
"list_view": "Vue liste",
|
|
140
|
+
"detail_view": "Vue détaillée",
|
|
141
|
+
"global": "Global",
|
|
142
|
+
"move_down": "Descendre",
|
|
143
|
+
"move_up": "Monter",
|
|
144
|
+
"toggle_select": "Basculer la sélection",
|
|
145
|
+
"open_ticket": "Ouvrir le ticket",
|
|
146
|
+
"reply": "Répondre",
|
|
147
|
+
"internal_note": "Note interne",
|
|
148
|
+
"change_status": "Changer le statut",
|
|
149
|
+
"change_priority": "Changer la priorité",
|
|
150
|
+
"follow_unfollow": "Suivre/Ne plus suivre",
|
|
151
|
+
"show_help": "Afficher cette aide",
|
|
152
|
+
"esc_to_close": "Appuyez sur Échap pour fermer"
|
|
153
|
+
},
|
|
154
|
+
"sla": {
|
|
155
|
+
"overdue": "En retard",
|
|
156
|
+
"breached": "SLA dépassé",
|
|
157
|
+
"first_response": "Première réponse",
|
|
158
|
+
"resolution": "Résolution"
|
|
159
|
+
},
|
|
160
|
+
"follow": {
|
|
161
|
+
"following": "Suivi",
|
|
162
|
+
"follow": "Suivre"
|
|
163
|
+
},
|
|
164
|
+
"file": {
|
|
165
|
+
"drop_or_browse": "Déposez les fichiers ici ou :link",
|
|
166
|
+
"browse": "parcourir"
|
|
167
|
+
},
|
|
168
|
+
"macro": {
|
|
169
|
+
"apply_macro": "Appliquer une macro",
|
|
170
|
+
"macros": "Macros",
|
|
171
|
+
"no_macros": "Aucune macro disponible"
|
|
172
|
+
},
|
|
173
|
+
"form": {
|
|
174
|
+
"name": "Nom",
|
|
175
|
+
"description": "Description",
|
|
176
|
+
"slug": "Identifiant",
|
|
177
|
+
"auto_generated": "Généré automatiquement si vide",
|
|
178
|
+
"active": "Actif",
|
|
179
|
+
"update": "Mettre à jour",
|
|
180
|
+
"create": "Créer",
|
|
181
|
+
"save": "Enregistrer",
|
|
182
|
+
"cancel": "Annuler",
|
|
183
|
+
"edit": "Modifier",
|
|
184
|
+
"delete": "Supprimer",
|
|
185
|
+
"actions": "Actions",
|
|
186
|
+
"order": "Ordre",
|
|
187
|
+
"value": "Valeur",
|
|
188
|
+
"yes": "Oui",
|
|
189
|
+
"no": "Non",
|
|
190
|
+
"confirm": "Confirmer",
|
|
191
|
+
"none": "Aucun",
|
|
192
|
+
"general": "Général",
|
|
193
|
+
"shared": "Partagé",
|
|
194
|
+
"private": "Privé",
|
|
195
|
+
"color": "Couleur",
|
|
196
|
+
"title": "Titre",
|
|
197
|
+
"copy": "Copier",
|
|
198
|
+
"dismiss": "Fermer",
|
|
199
|
+
"saved": "Enregistré.",
|
|
200
|
+
"saving": "Enregistrement...",
|
|
201
|
+
"creating": "Création...",
|
|
202
|
+
"submitting": "Envoi en cours...",
|
|
203
|
+
"sending": "Envoi...",
|
|
204
|
+
"uploading": "Téléversement...",
|
|
205
|
+
"copied": "Copié !",
|
|
206
|
+
"subject": "Sujet",
|
|
207
|
+
"priority": "Priorité",
|
|
208
|
+
"department": "Département",
|
|
209
|
+
"attachments": "Pièces jointes"
|
|
210
|
+
},
|
|
211
|
+
"presence": {
|
|
212
|
+
"also_viewing": "Consultent également : :names"
|
|
213
|
+
},
|
|
214
|
+
"pinned_notes": {
|
|
215
|
+
"title": "Notes épinglées",
|
|
216
|
+
"unpin": "Désépingler"
|
|
217
|
+
},
|
|
218
|
+
"admin": {
|
|
219
|
+
"settings": {
|
|
220
|
+
"title": "Paramètres",
|
|
221
|
+
"general": "Général",
|
|
222
|
+
"guest_tickets": "Tickets invités",
|
|
223
|
+
"guest_tickets_desc": "Permettre aux visiteurs de soumettre des tickets sans se connecter",
|
|
224
|
+
"allow_customer_close": "Autoriser la fermeture par le client",
|
|
225
|
+
"allow_customer_close_desc": "Permettre aux clients de fermer leurs propres tickets",
|
|
226
|
+
"auto_close": "Fermeture automatique des tickets résolus après",
|
|
227
|
+
"auto_close_desc": "Nombre de jours après la résolution avant la fermeture automatique (0 pour désactiver)",
|
|
228
|
+
"days": "jours",
|
|
229
|
+
"limits": "Limites",
|
|
230
|
+
"max_attachments": "Nombre maximal de pièces jointes par réponse",
|
|
231
|
+
"max_attachment_size": "Taille maximale des pièces jointes",
|
|
232
|
+
"tickets": "Tickets",
|
|
233
|
+
"reference_prefix": "Préfixe de référence",
|
|
234
|
+
"reference_prefix_desc": "Préfixe pour les références de tickets (ex. ESC produit ESC-00001)",
|
|
235
|
+
"inbound_email": "E-mail entrant",
|
|
236
|
+
"inbound_email_desc": "Permettre la création et la réponse aux tickets par e-mail",
|
|
237
|
+
"email_provider": "Fournisseur d'e-mail",
|
|
238
|
+
"email_provider_desc": "Mode de réception des e-mails entrants",
|
|
239
|
+
"support_email": "Adresse e-mail du support",
|
|
240
|
+
"support_email_desc": "L'adresse e-mail à laquelle les clients enverront leurs messages (ex. support@exemple.com)",
|
|
241
|
+
"signing_key": "Clé de signature",
|
|
242
|
+
"webhook_url": "URL du webhook",
|
|
243
|
+
"mailgun_webhook_desc": "Configurez cette URL dans votre tableau de bord Mailgun",
|
|
244
|
+
"inbound_token": "Jeton entrant",
|
|
245
|
+
"postmark_webhook_desc": "Configurez cette URL dans les paramètres de votre serveur Postmark",
|
|
246
|
+
"region": "Région",
|
|
247
|
+
"topic_arn": "ARN du sujet",
|
|
248
|
+
"ses_webhook_desc": "Configurez cette URL comme point de terminaison de votre abonnement SNS",
|
|
249
|
+
"host": "Hôte",
|
|
250
|
+
"port": "Port",
|
|
251
|
+
"encryption": "Chiffrement",
|
|
252
|
+
"username": "Nom d'utilisateur",
|
|
253
|
+
"password": "Mot de passe",
|
|
254
|
+
"mailbox": "Boîte de réception",
|
|
255
|
+
"save_settings": "Enregistrer les paramètres"
|
|
256
|
+
},
|
|
257
|
+
"reports": {
|
|
258
|
+
"title": "Rapports",
|
|
259
|
+
"last_days": ":count derniers jours",
|
|
260
|
+
"total_tickets": "Total des tickets",
|
|
261
|
+
"resolved": "Résolus",
|
|
262
|
+
"avg_first_response": "Temps moyen de première réponse",
|
|
263
|
+
"sla_breaches": "Dépassements SLA",
|
|
264
|
+
"by_status": "Par statut",
|
|
265
|
+
"by_priority": "Par priorité",
|
|
266
|
+
"no_data": "Aucune donnée de ticket pour cette période",
|
|
267
|
+
"no_ratings": "Aucune évaluation pour cette période",
|
|
268
|
+
"ratings_count": "(:count évaluations)"
|
|
269
|
+
},
|
|
270
|
+
"departments": {
|
|
271
|
+
"title": "Départements",
|
|
272
|
+
"add": "Ajouter un département",
|
|
273
|
+
"edit_title": "Modifier le département",
|
|
274
|
+
"new_title": "Nouveau département",
|
|
275
|
+
"tickets": "Tickets",
|
|
276
|
+
"agents": "Agents",
|
|
277
|
+
"no_departments": "Aucun département pour le moment",
|
|
278
|
+
"no_departments_desc": "Créez votre premier département pour organiser les tickets",
|
|
279
|
+
"confirm_delete": "Supprimer ce département ?"
|
|
280
|
+
},
|
|
281
|
+
"sla_policies": {
|
|
282
|
+
"title": "Politiques SLA",
|
|
283
|
+
"add": "Ajouter une politique",
|
|
284
|
+
"edit_title": "Modifier la politique SLA",
|
|
285
|
+
"new_title": "Nouvelle politique SLA",
|
|
286
|
+
"default": "Par défaut",
|
|
287
|
+
"business_hours": "Heures ouvrées",
|
|
288
|
+
"business_hours_only": "Heures ouvrées uniquement",
|
|
289
|
+
"default_policy": "Politique par défaut",
|
|
290
|
+
"first_response_hours": "Heures de première réponse (par priorité)",
|
|
291
|
+
"resolution_hours": "Heures de résolution (par priorité)",
|
|
292
|
+
"no_policies": "Aucune politique SLA pour le moment",
|
|
293
|
+
"no_policies_desc": "Définissez des objectifs de temps de réponse et de résolution",
|
|
294
|
+
"confirm_delete": "Supprimer cette politique SLA ?"
|
|
295
|
+
},
|
|
296
|
+
"escalation_rules": {
|
|
297
|
+
"title": "Règles d'escalade",
|
|
298
|
+
"add": "Ajouter une règle",
|
|
299
|
+
"edit_title": "Modifier la règle d'escalade",
|
|
300
|
+
"new_title": "Nouvelle règle d'escalade",
|
|
301
|
+
"trigger": "Déclencheur",
|
|
302
|
+
"trigger_type": "Type de déclencheur",
|
|
303
|
+
"time_based": "Basé sur le temps",
|
|
304
|
+
"sla_breach": "Dépassement SLA",
|
|
305
|
+
"priority_based": "Basé sur la priorité",
|
|
306
|
+
"conditions": "Conditions",
|
|
307
|
+
"add_short": "+ Ajouter",
|
|
308
|
+
"status": "Statut",
|
|
309
|
+
"priority": "Priorité",
|
|
310
|
+
"assignment": "Assignation",
|
|
311
|
+
"age_hours": "Ancienneté (heures)",
|
|
312
|
+
"no_response_hours": "Sans réponse (heures)",
|
|
313
|
+
"sla_breached": "SLA dépassé",
|
|
314
|
+
"escalate": "Escalader",
|
|
315
|
+
"change_priority": "Changer la priorité",
|
|
316
|
+
"assign_to": "Assigner à",
|
|
317
|
+
"change_department": "Changer le département",
|
|
318
|
+
"no_rules": "Aucune règle d'escalade pour le moment",
|
|
319
|
+
"no_rules_desc": "Configurez l'escalade automatique pour les tickets en retard",
|
|
320
|
+
"confirm_delete": "Supprimer cette règle d'escalade ?"
|
|
321
|
+
},
|
|
322
|
+
"canned_responses": {
|
|
323
|
+
"title": "Réponses prédéfinies",
|
|
324
|
+
"add": "Ajouter une réponse",
|
|
325
|
+
"title_placeholder": "Titre",
|
|
326
|
+
"body_placeholder": "Corps de la réponse...",
|
|
327
|
+
"category_placeholder": "Catégorie (facultatif)",
|
|
328
|
+
"no_responses": "Aucune réponse prédéfinie pour le moment",
|
|
329
|
+
"no_responses_desc": "Créez des modèles réutilisables pour les réponses courantes",
|
|
330
|
+
"confirm_delete": "Supprimer cette réponse prédéfinie ?"
|
|
331
|
+
},
|
|
332
|
+
"tags": {
|
|
333
|
+
"title": "Étiquettes",
|
|
334
|
+
"add": "Ajouter une étiquette",
|
|
335
|
+
"no_tags": "Aucune étiquette pour le moment",
|
|
336
|
+
"no_tags_desc": "Créez des étiquettes pour catégoriser et filtrer les tickets",
|
|
337
|
+
"confirm_delete": "Supprimer cette étiquette ?"
|
|
338
|
+
},
|
|
339
|
+
"macros": {
|
|
340
|
+
"title": "Macros",
|
|
341
|
+
"subtitle": "Les macros automatisent les actions répétitives sur les tickets.",
|
|
342
|
+
"create_macro": "Créer une macro",
|
|
343
|
+
"update_macro": "Mettre à jour la macro",
|
|
344
|
+
"shared_label": "Partagée avec tous les agents",
|
|
345
|
+
"add_action": "+ Ajouter une action",
|
|
346
|
+
"no_actions": "Aucune action pour le moment. Ajoutez des actions pour définir le comportement de cette macro.",
|
|
347
|
+
"set_status": "Définir le statut",
|
|
348
|
+
"set_priority": "Définir la priorité",
|
|
349
|
+
"assign_to": "Assigner à",
|
|
350
|
+
"add_tags": "Ajouter des étiquettes",
|
|
351
|
+
"move_to_department": "Déplacer vers le département",
|
|
352
|
+
"send_reply": "Envoyer une réponse",
|
|
353
|
+
"add_internal_note": "Ajouter une note interne",
|
|
354
|
+
"select_status": "Sélectionner le statut...",
|
|
355
|
+
"select_priority": "Sélectionner la priorité...",
|
|
356
|
+
"agent_placeholder": "Identifiant ou e-mail de l'agent",
|
|
357
|
+
"tags_placeholder": "Noms d'étiquettes séparés par des virgules",
|
|
358
|
+
"department_placeholder": "Identifiant ou nom du département",
|
|
359
|
+
"reply_placeholder": "Message de réponse...",
|
|
360
|
+
"note_placeholder": "Contenu de la note...",
|
|
361
|
+
"no_macros": "Aucune macro pour le moment",
|
|
362
|
+
"no_macros_desc": "Créez des macros pour automatiser les actions répétitives sur les tickets",
|
|
363
|
+
"creator": "Créateur",
|
|
364
|
+
"confirm_delete": "Supprimer cette macro ? Cette action est irréversible."
|
|
365
|
+
},
|
|
366
|
+
"api_tokens": {
|
|
367
|
+
"title": "Jetons API",
|
|
368
|
+
"subtitle": "Gérez les jetons API pour les applications de bureau et les intégrations externes.",
|
|
369
|
+
"create_token": "Créer un jeton",
|
|
370
|
+
"api_disabled": "L'API REST est actuellement désactivée. Définissez ESCALATED_API_ENABLED=true dans votre fichier .env pour l'activer.",
|
|
371
|
+
"token_created": "Jeton créé ! Copiez-le maintenant — il ne sera plus affiché.",
|
|
372
|
+
"new_token": "Nouveau jeton API",
|
|
373
|
+
"token_name": "Nom du jeton",
|
|
374
|
+
"token_name_placeholder": "ex. Application de bureau - Ordinateur professionnel",
|
|
375
|
+
"user": "Utilisateur",
|
|
376
|
+
"select_user": "Sélectionner un utilisateur...",
|
|
377
|
+
"abilities": "Permissions",
|
|
378
|
+
"expires_in": "Expire dans (jours)",
|
|
379
|
+
"no_expiration": "Laissez vide pour aucune expiration",
|
|
380
|
+
"last_used": "Dernière utilisation",
|
|
381
|
+
"expires": "Expire",
|
|
382
|
+
"never": "Jamais",
|
|
383
|
+
"expired": "Expiré",
|
|
384
|
+
"revoke": "Révoquer",
|
|
385
|
+
"no_tokens": "Aucun jeton API créé pour le moment.",
|
|
386
|
+
"confirm_revoke": "Êtes-vous sûr de vouloir révoquer ce jeton ? Cette action est irréversible."
|
|
387
|
+
},
|
|
388
|
+
"plugins": {
|
|
389
|
+
"title": "Extensions",
|
|
390
|
+
"upload": "Téléverser une extension",
|
|
391
|
+
"plugin_zip": "Fichier ZIP de l'extension",
|
|
392
|
+
"installed": "Extensions installées",
|
|
393
|
+
"no_plugins": "Aucune extension installée",
|
|
394
|
+
"no_plugins_alt": "Aucune extension installée pour le moment.",
|
|
395
|
+
"no_plugins_desc": "Téléversez un fichier ZIP d'extension pour enrichir Escalated avec des fonctionnalités personnalisées.",
|
|
396
|
+
"active": "Active",
|
|
397
|
+
"inactive": "Inactive",
|
|
398
|
+
"installed_date": "Installée le :date",
|
|
399
|
+
"menu_items": ":count élément(s) de menu",
|
|
400
|
+
"widgets": ":count widget(s)",
|
|
401
|
+
"page_components": ":count composant(s) de page",
|
|
402
|
+
"confirm_delete": "Êtes-vous sûr de vouloir supprimer cette extension ? Cette action est irréversible."
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
"agent": {
|
|
406
|
+
"dashboard": "Tableau de bord agent",
|
|
407
|
+
"open_tickets": "Tickets ouverts",
|
|
408
|
+
"my_assigned": "Mes assignations",
|
|
409
|
+
"unassigned": "Non assignés",
|
|
410
|
+
"sla_breached": "SLA dépassés",
|
|
411
|
+
"resolved_today": "Résolus aujourd'hui",
|
|
412
|
+
"needs_attention": "Nécessite une attention",
|
|
413
|
+
"sla_breaching": "SLA en dépassement",
|
|
414
|
+
"unassigned_urgent": "Non assignés urgents",
|
|
415
|
+
"recent_tickets": "Tickets récents",
|
|
416
|
+
"no_recent_tickets": "Aucun ticket récent",
|
|
417
|
+
"my_performance": "Ma performance",
|
|
418
|
+
"resolved_this_week": "Résolus cette semaine"
|
|
419
|
+
},
|
|
420
|
+
"guest": {
|
|
421
|
+
"submit_title": "Soumettre un ticket",
|
|
422
|
+
"submit_heading": "Soumettre un ticket de support",
|
|
423
|
+
"submit_desc": "Aucun compte nécessaire. Nous vous fournirons un lien pour suivre votre ticket.",
|
|
424
|
+
"sign_in_link": "Vous avez déjà un compte ? Connectez-vous",
|
|
425
|
+
"your_name": "Votre nom",
|
|
426
|
+
"email_address": "Adresse e-mail",
|
|
427
|
+
"bookmark_title": "Ajoutez cette page à vos favoris",
|
|
428
|
+
"bookmark_desc": "Ceci est votre lien privé pour consulter et répondre à ce ticket.",
|
|
429
|
+
"copy_link": "Copier le lien",
|
|
430
|
+
"write_reply": "Rédigez votre réponse..."
|
|
431
|
+
},
|
|
432
|
+
"customer": {
|
|
433
|
+
"replies": "Réponses",
|
|
434
|
+
"reply": "Répondre"
|
|
435
|
+
},
|
|
436
|
+
"common": {
|
|
437
|
+
"change_status": "Changer le statut...",
|
|
438
|
+
"change_priority": "Changer la priorité...",
|
|
439
|
+
"kb": "Ko",
|
|
440
|
+
"upload": "Téléverser"
|
|
441
|
+
}
|
|
442
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, inject } from 'vue';
|
|
3
|
+
import { useForm, usePage } from '@inertiajs/vue3';
|
|
4
|
+
import EscalatedLayout from '../../../components/EscalatedLayout.vue';
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
tokens: Array,
|
|
8
|
+
users: Array,
|
|
9
|
+
api_enabled: Boolean,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const dark = inject('esc-dark', computed(() => true));
|
|
13
|
+
const page = usePage();
|
|
14
|
+
const prefix = computed(() => {
|
|
15
|
+
const p = page.props.escalated?.prefix || 'support';
|
|
16
|
+
return p.startsWith('/') ? p : `/${p}`;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const showCreate = ref(false);
|
|
20
|
+
const copiedToken = ref(null);
|
|
21
|
+
|
|
22
|
+
const form = useForm({
|
|
23
|
+
name: '',
|
|
24
|
+
user_id: '',
|
|
25
|
+
abilities: ['agent'],
|
|
26
|
+
expires_in_days: null,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function createToken() {
|
|
30
|
+
form.post(`${prefix.value}/admin/api-tokens`, {
|
|
31
|
+
preserveScroll: true,
|
|
32
|
+
onSuccess: (page) => {
|
|
33
|
+
const flash = page.props.flash || {};
|
|
34
|
+
if (flash.plain_text_token) {
|
|
35
|
+
copiedToken.value = flash.plain_text_token;
|
|
36
|
+
}
|
|
37
|
+
form.reset();
|
|
38
|
+
showCreate.value = false;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function revokeToken(id) {
|
|
44
|
+
if (!confirm('Are you sure you want to revoke this token? This cannot be undone.')) return;
|
|
45
|
+
useForm({}).delete(`${prefix.value}/admin/api-tokens/${id}`, { preserveScroll: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function copyToken() {
|
|
49
|
+
if (copiedToken.value) {
|
|
50
|
+
navigator.clipboard.writeText(copiedToken.value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dismissToken() {
|
|
55
|
+
copiedToken.value = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function toggleAbility(ability) {
|
|
59
|
+
const idx = form.abilities.indexOf(ability);
|
|
60
|
+
if (idx >= 0) {
|
|
61
|
+
form.abilities.splice(idx, 1);
|
|
62
|
+
} else {
|
|
63
|
+
form.abilities.push(ability);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatDate(iso) {
|
|
68
|
+
if (!iso) return '—';
|
|
69
|
+
return new Date(iso).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<EscalatedLayout title="API Tokens">
|
|
75
|
+
<div class="space-y-6">
|
|
76
|
+
<!-- Header -->
|
|
77
|
+
<div class="flex items-center justify-between">
|
|
78
|
+
<div>
|
|
79
|
+
<h2 :class="dark ? 'text-white' : 'text-gray-900'" class="text-lg font-semibold">API Tokens</h2>
|
|
80
|
+
<p :class="dark ? 'text-neutral-500' : 'text-gray-500'" class="mt-1 text-sm">Manage API tokens for desktop apps and external integrations.</p>
|
|
81
|
+
</div>
|
|
82
|
+
<button @click="showCreate = true"
|
|
83
|
+
:class="dark ? 'bg-white text-black hover:bg-neutral-200' : 'bg-gray-900 text-white hover:bg-gray-800'"
|
|
84
|
+
class="rounded-lg px-4 py-2 text-sm font-medium transition-colors">
|
|
85
|
+
Create Token
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Not enabled warning -->
|
|
90
|
+
<div v-if="!api_enabled"
|
|
91
|
+
:class="dark ? 'border-amber-500/30 bg-amber-500/10 text-amber-300' : 'border-amber-300 bg-amber-50 text-amber-800'"
|
|
92
|
+
class="rounded-lg border p-4 text-sm">
|
|
93
|
+
The REST API is currently disabled. Set <code class="font-mono">ESCALATED_API_ENABLED=true</code> in your .env to enable it.
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- Copied token modal -->
|
|
97
|
+
<div v-if="copiedToken"
|
|
98
|
+
:class="dark ? 'border-emerald-500/30 bg-emerald-500/10' : 'border-emerald-300 bg-emerald-50'"
|
|
99
|
+
class="rounded-lg border p-4">
|
|
100
|
+
<p :class="dark ? 'text-emerald-300' : 'text-emerald-800'" class="text-sm font-medium">Token created! Copy it now — it won't be shown again.</p>
|
|
101
|
+
<div class="mt-2 flex items-center gap-2">
|
|
102
|
+
<code :class="dark ? 'bg-black text-emerald-400' : 'bg-white text-emerald-700'"
|
|
103
|
+
class="flex-1 rounded border px-3 py-2 text-xs font-mono break-all"
|
|
104
|
+
:style="dark ? 'border-color: rgba(255,255,255,0.1)' : ''">{{ copiedToken }}</code>
|
|
105
|
+
<button @click="copyToken"
|
|
106
|
+
:class="dark ? 'bg-white/10 text-white hover:bg-white/20' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
|
|
107
|
+
class="rounded-lg px-3 py-2 text-sm font-medium transition-colors">Copy</button>
|
|
108
|
+
<button @click="dismissToken"
|
|
109
|
+
:class="dark ? 'text-neutral-500 hover:text-white' : 'text-gray-400 hover:text-gray-600'"
|
|
110
|
+
class="px-2 py-2 text-sm transition-colors">Dismiss</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Create form -->
|
|
115
|
+
<div v-if="showCreate"
|
|
116
|
+
:class="dark ? 'border-white/[0.06] bg-neutral-950' : 'border-gray-200 bg-white'"
|
|
117
|
+
class="rounded-lg border p-5 space-y-4">
|
|
118
|
+
<h3 :class="dark ? 'text-white' : 'text-gray-900'" class="text-sm font-semibold">New API Token</h3>
|
|
119
|
+
|
|
120
|
+
<div class="grid grid-cols-2 gap-4">
|
|
121
|
+
<div>
|
|
122
|
+
<label :class="dark ? 'text-neutral-400' : 'text-gray-600'" class="mb-1 block text-xs font-medium">Token Name</label>
|
|
123
|
+
<input v-model="form.name" type="text" placeholder="e.g. Desktop App - Work Laptop"
|
|
124
|
+
:class="dark ? 'border-white/[0.08] bg-white/[0.04] text-white' : 'border-gray-300 bg-white text-gray-900'"
|
|
125
|
+
class="w-full rounded-lg border px-3 py-2 text-sm" />
|
|
126
|
+
</div>
|
|
127
|
+
<div>
|
|
128
|
+
<label :class="dark ? 'text-neutral-400' : 'text-gray-600'" class="mb-1 block text-xs font-medium">User</label>
|
|
129
|
+
<select v-model="form.user_id"
|
|
130
|
+
:class="dark ? 'border-white/[0.08] bg-white/[0.04] text-white' : 'border-gray-300 bg-white text-gray-900'"
|
|
131
|
+
class="w-full rounded-lg border px-3 py-2 text-sm">
|
|
132
|
+
<option value="">Select user...</option>
|
|
133
|
+
<option v-for="u in users" :key="u.id" :value="u.id">{{ u.name }} ({{ u.email }})</option>
|
|
134
|
+
</select>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div>
|
|
139
|
+
<label :class="dark ? 'text-neutral-400' : 'text-gray-600'" class="mb-1 block text-xs font-medium">Abilities</label>
|
|
140
|
+
<div class="flex items-center gap-3">
|
|
141
|
+
<label v-for="ability in ['agent', 'admin']" :key="ability"
|
|
142
|
+
:class="dark ? 'text-neutral-300' : 'text-gray-700'"
|
|
143
|
+
class="flex items-center gap-1.5 text-sm cursor-pointer">
|
|
144
|
+
<input type="checkbox" :checked="form.abilities.includes(ability)" @change="toggleAbility(ability)"
|
|
145
|
+
class="rounded border-neutral-600" />
|
|
146
|
+
{{ ability }}
|
|
147
|
+
</label>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div>
|
|
152
|
+
<label :class="dark ? 'text-neutral-400' : 'text-gray-600'" class="mb-1 block text-xs font-medium">Expires In (days)</label>
|
|
153
|
+
<input v-model.number="form.expires_in_days" type="number" min="1" max="365" placeholder="Leave empty for no expiration"
|
|
154
|
+
:class="dark ? 'border-white/[0.08] bg-white/[0.04] text-white' : 'border-gray-300 bg-white text-gray-900'"
|
|
155
|
+
class="w-48 rounded-lg border px-3 py-2 text-sm" />
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="flex items-center gap-3 pt-2">
|
|
159
|
+
<button @click="createToken" :disabled="form.processing || !form.name || !form.user_id"
|
|
160
|
+
:class="dark ? 'bg-white text-black hover:bg-neutral-200 disabled:bg-neutral-700 disabled:text-neutral-500' : 'bg-gray-900 text-white hover:bg-gray-800 disabled:bg-gray-300 disabled:text-gray-500'"
|
|
161
|
+
class="rounded-lg px-4 py-2 text-sm font-medium transition-colors">
|
|
162
|
+
{{ form.processing ? 'Creating...' : 'Create Token' }}
|
|
163
|
+
</button>
|
|
164
|
+
<button @click="showCreate = false"
|
|
165
|
+
:class="dark ? 'text-neutral-500 hover:text-white' : 'text-gray-500 hover:text-gray-900'"
|
|
166
|
+
class="text-sm transition-colors">Cancel</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Tokens table -->
|
|
171
|
+
<div :class="dark ? 'border-white/[0.06]' : 'border-gray-200'" class="overflow-hidden rounded-lg border">
|
|
172
|
+
<table class="w-full text-sm">
|
|
173
|
+
<thead :class="dark ? 'bg-white/[0.03] text-neutral-500' : 'bg-gray-50 text-gray-500'">
|
|
174
|
+
<tr>
|
|
175
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase">Name</th>
|
|
176
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase">User</th>
|
|
177
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase">Abilities</th>
|
|
178
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase">Last Used</th>
|
|
179
|
+
<th class="px-4 py-3 text-left text-xs font-medium uppercase">Expires</th>
|
|
180
|
+
<th class="px-4 py-3 text-right text-xs font-medium uppercase">Actions</th>
|
|
181
|
+
</tr>
|
|
182
|
+
</thead>
|
|
183
|
+
<tbody :class="dark ? 'divide-white/[0.04]' : 'divide-gray-100'" class="divide-y">
|
|
184
|
+
<tr v-for="token in tokens" :key="token.id"
|
|
185
|
+
:class="[dark ? 'hover:bg-white/[0.02]' : 'hover:bg-gray-50', token.is_expired ? 'opacity-50' : '']">
|
|
186
|
+
<td :class="dark ? 'text-white' : 'text-gray-900'" class="px-4 py-3 font-medium">{{ token.name }}</td>
|
|
187
|
+
<td :class="dark ? 'text-neutral-400' : 'text-gray-600'" class="px-4 py-3">{{ token.user_name }}</td>
|
|
188
|
+
<td class="px-4 py-3">
|
|
189
|
+
<span v-for="a in (token.abilities || [])" :key="a"
|
|
190
|
+
:class="dark ? 'bg-white/[0.06] text-neutral-300' : 'bg-gray-100 text-gray-700'"
|
|
191
|
+
class="mr-1 rounded px-1.5 py-0.5 text-xs">{{ a }}</span>
|
|
192
|
+
</td>
|
|
193
|
+
<td :class="dark ? 'text-neutral-500' : 'text-gray-500'" class="px-4 py-3 text-xs">
|
|
194
|
+
<template v-if="token.last_used_at">{{ formatDate(token.last_used_at) }}</template>
|
|
195
|
+
<template v-else>Never</template>
|
|
196
|
+
</td>
|
|
197
|
+
<td :class="dark ? 'text-neutral-500' : 'text-gray-500'" class="px-4 py-3 text-xs">
|
|
198
|
+
<span v-if="token.is_expired" class="text-rose-400">Expired</span>
|
|
199
|
+
<template v-else-if="token.expires_at">{{ formatDate(token.expires_at) }}</template>
|
|
200
|
+
<template v-else>Never</template>
|
|
201
|
+
</td>
|
|
202
|
+
<td class="px-4 py-3 text-right">
|
|
203
|
+
<button @click="revokeToken(token.id)"
|
|
204
|
+
class="text-xs text-rose-400 hover:text-rose-300 transition-colors">Revoke</button>
|
|
205
|
+
</td>
|
|
206
|
+
</tr>
|
|
207
|
+
<tr v-if="!tokens.length">
|
|
208
|
+
<td colspan="6" :class="dark ? 'text-neutral-500' : 'text-gray-400'" class="px-4 py-8 text-center text-sm">
|
|
209
|
+
No API tokens created yet.
|
|
210
|
+
</td>
|
|
211
|
+
</tr>
|
|
212
|
+
</tbody>
|
|
213
|
+
</table>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</EscalatedLayout>
|
|
217
|
+
</template>
|