{
+ mounted() {
+ this.getConfig();
+ this.getAudioProfiles();
this.getStatus();
+ this.getHistory();
+ this.getVoicemails();
this.getVoicemailStatus();
- }, 1000);
+ this.getRingtones();
+ this.getRingtoneStatus();
+
+ // poll for status
+ this.statusInterval = setInterval(() => {
+ this.getStatus();
+ this.getVoicemailStatus();
+ this.getRingtoneStatus();
+ }, 1000);
// poll for history/voicemails less frequently
this.historyInterval = setInterval(() => {
@@ -786,6 +957,17 @@ export default {
console.log(e);
}
},
+ async clearHistory() {
+ if (!confirm(this.$t("common.delete_confirm"))) return;
+ try {
+ await window.axios.delete("/api/v1/telephone/history");
+ this.callHistory = [];
+ ToastUtils.success("Call history cleared");
+ } catch (e) {
+ console.error(e);
+ ToastUtils.error("Failed to clear call history");
+ }
+ },
async getVoicemailStatus() {
try {
const response = await window.axios.get("/api/v1/telephone/voicemail/status");
@@ -794,6 +976,115 @@ export default {
console.log(e);
}
},
+ async getRingtoneStatus() {
+ try {
+ const response = await window.axios.get("/api/v1/telephone/ringtones/status");
+ this.ringtoneStatus = response.data;
+ } catch (e) {
+ console.log(e);
+ }
+ },
+ async getRingtones() {
+ try {
+ const response = await window.axios.get("/api/v1/telephone/ringtones");
+ this.ringtones = response.data;
+ } catch (e) {
+ console.error("Failed to get ringtones:", e);
+ }
+ },
+ async deleteRingtone(ringtone) {
+ if (!confirm(this.$t("common.delete_confirm"))) return;
+ try {
+ await window.axios.delete(`/api/v1/telephone/ringtones/${ringtone.id}`);
+ ToastUtils.success(this.$t("call.ringtone_deleted"));
+ await this.getRingtones();
+ await this.getRingtoneStatus();
+ } catch (e) {
+ console.error(e);
+ ToastUtils.error(this.$t("call.failed_to_delete_ringtone"));
+ }
+ },
+ async setPrimaryRingtone(ringtone) {
+ try {
+ await window.axios.patch(`/api/v1/telephone/ringtones/${ringtone.id}`, {
+ is_primary: true,
+ });
+ ToastUtils.success(this.$t("call.primary_ringtone_set"));
+ await this.getRingtones();
+ await this.getRingtoneStatus();
+ } catch (e) {
+ console.error(e);
+ ToastUtils.error(this.$t("call.failed_to_set_primary_ringtone"));
+ }
+ },
+ startEditingRingtone(ringtone) {
+ this.editingRingtoneId = ringtone.id;
+ this.editingRingtoneName = ringtone.display_name;
+ },
+ async saveRingtoneName() {
+ try {
+ await window.axios.patch(`/api/v1/telephone/ringtones/${this.editingRingtoneId}`, {
+ display_name: this.editingRingtoneName,
+ });
+ this.editingRingtoneId = null;
+ await this.getRingtones();
+ } catch (e) {
+ console.error(e);
+ ToastUtils.error(this.$t("call.failed_to_update_ringtone_name"));
+ }
+ },
+ async uploadRingtone(event) {
+ const file = event.target.files[0];
+ if (!file) return;
+
+ this.isUploadingRingtone = true;
+ const formData = new FormData();
+ formData.append("file", file);
+
+ try {
+ await window.axios.post("/api/v1/telephone/ringtones/upload", formData, {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ });
+ ToastUtils.success(this.$t("call.ringtone_uploaded_successfully"));
+ await this.getRingtones();
+ await this.getRingtoneStatus();
+ } catch (e) {
+ console.error(e);
+ ToastUtils.error(e.response?.data?.message || this.$t("call.failed_to_upload_ringtone"));
+ } finally {
+ this.isUploadingRingtone = false;
+ event.target.value = "";
+ }
+ },
+ async playRingtonePreview(ringtone) {
+ if (this.isPlayingRingtone && this.playingRingtoneId === ringtone.id) {
+ this.audioPlayer.pause();
+ this.isPlayingRingtone = false;
+ this.playingRingtoneId = null;
+ return;
+ }
+
+ if (this.audioPlayer) {
+ this.audioPlayer.pause();
+ }
+
+ this.playingRingtoneId = ringtone.id;
+ this.audioPlayer = new Audio(`/api/v1/telephone/ringtones/${ringtone.id}/audio`);
+ this.audioPlayer.onended = () => {
+ this.isPlayingRingtone = false;
+ this.playingRingtoneId = null;
+ };
+
+ try {
+ await this.audioPlayer.play();
+ this.isPlayingRingtone = true;
+ } catch (e) {
+ console.error(e);
+ ToastUtils.error(this.$t("call.failed_to_play_ringtone"));
+ }
+ },
async getVoicemails() {
try {
const response = await window.axios.get("/api/v1/telephone/voicemails");
@@ -845,7 +1136,7 @@ export default {
await window.axios.delete("/api/v1/telephone/voicemail/greeting");
ToastUtils.success("Greeting deleted");
await this.getVoicemailStatus();
- } catch (e) {
+ } catch {
ToastUtils.error("Failed to delete greeting");
}
},
@@ -901,7 +1192,7 @@ export default {
this.isPlayingGreeting = true;
this.audioPlayer = new Audio("/api/v1/telephone/voicemail/greeting/audio");
this.audioPlayer.play().catch(() => {
- ToastUtils.error("No greeting audio found. Please generate one first.");
+ ToastUtils.error(this.$t("call.no_greeting_audio_found"));
this.isPlayingGreeting = false;
});
this.audioPlayer.onended = () => {
diff --git a/meshchatx/src/frontend/components/map/MapPage.vue b/meshchatx/src/frontend/components/map/MapPage.vue
index 20f574e..c8f3b2e 100644
--- a/meshchatx/src/frontend/components/map/MapPage.vue
+++ b/meshchatx/src/frontend/components/map/MapPage.vue
@@ -638,7 +638,7 @@ import XYZ from "ol/source/XYZ";
import VectorSource from "ol/source/Vector";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
-import { Style, Icon, Text, Fill, Stroke, Circle as CircleStyle } from "ol/style";
+import { Style, Text, Fill, Stroke, Circle as CircleStyle } from "ol/style";
import { fromLonLat, toLonLat } from "ol/proj";
import { defaults as defaultControls } from "ol/control";
import DragBox from "ol/interaction/DragBox";
diff --git a/meshchatx/src/frontend/components/settings/IdentitiesPage.vue b/meshchatx/src/frontend/components/settings/IdentitiesPage.vue
new file mode 100644
index 0000000..79c4224
--- /dev/null
+++ b/meshchatx/src/frontend/components/settings/IdentitiesPage.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+
+
+ {{ $t("identities.title") }}
+
+
+ {{ $t("identities.manage") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ identity.display_name }}
+
+
+ {{ $t("identities.current") }}
+
+
+
+ {{ identity.hash }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("identities.no_identities") }}
+
+
{{ $t("identities.create_first") }}
+
+
+
+
+
+
+
+
+
+ {{ $t("identities.new_identity") }}
+
+
{{ $t("identities.generate_fresh") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+